From 567ec26c4813547cb2d09dfb13afb8bb186da07f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 24 Feb 2021 22:01:28 +0100 Subject: [PATCH 001/137] 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 a0aafaad3ce..f9a8e3e99b3 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 2fef4c4eef76fd70f2ccffeda4f5e24f51dd1fcc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 25 Feb 2021 01:16:20 -0600 Subject: [PATCH 002/137] 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 1dc5bf56c86..22db3c76273 100644 --- a/homeassistant/components/doorbird/__init__.py +++ b/homeassistant/components/doorbird/__init__.py @@ -34,6 +34,7 @@ from .const import ( 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 @@ class ConfiguredDoorBird: 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 af847dac673..46a95f0d500 100644 --- a/homeassistant/components/doorbird/const.py +++ b/homeassistant/components/doorbird/const.py @@ -17,3 +17,5 @@ DOORBIRD_INFO_KEY_DEVICE_TYPE = "DEVICE-TYPE" 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 f1f146aebb9..424bb79092f 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 003/137] 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 a3e4451312a..b2878c8143f 100644 --- a/homeassistant/components/hassio/const.py +++ b/homeassistant/components/hassio/const.py @@ -33,7 +33,8 @@ X_HASS_IS_ADMIN = "X-Hass-Is-Admin" 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 d2c0bc9ed10..387aa926489 100644 --- a/homeassistant/components/hassio/websocket_api.py +++ b/homeassistant/components/hassio/websocket_api.py @@ -7,6 +7,10 @@ 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 homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) from .const import ( ATTR_DATA, @@ -20,6 +24,7 @@ from .const import ( 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 ad9829f17ff..f3f35b62562 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 eaeed74fbf7..2efb5b0744e 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 00000000000..18da5df13ea --- /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 004/137] 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 c7880f9b416..59597750ce8 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 677ce0d0c2f..b90aa3ce936 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 28478c4349a..9773824d221 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 005/137] 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 8f99e6a7a6e..18220fc9e3a 100644 --- a/homeassistant/components/shelly/binary_sensor.py +++ b/homeassistant/components/shelly/binary_sensor.py @@ -73,6 +73,11 @@ SENSORS = { 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 @@ REST_SENSORS = { 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 32fb33877d3..472f3be4dae 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -133,7 +133,7 @@ SENSORS = { 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 @@ SENSORS = { 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 006/137] 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 cc58e31066a..75bc95b7fe4 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 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 248a34547b5..a40eb10de8b 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 685fe50c9b6..d0ed9eb5291 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -32,7 +32,7 @@ class ZWaveBaseEntity(Entity): 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 3634454544f..f2815bec7f6 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -18,7 +18,7 @@ from homeassistant.config_entries import ( 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 007/137] 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 992fce2ac87..f75594a546e 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 99227d81b66..00000000000 --- 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 0166d41ce0c..00000000000 --- 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 ac4ac2f9a16..a152086ea82 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 @@ from .objects import Input __all__ = [ "SECRET_YAML", - "_SECRET_NAMESPACE", "Input", "dump", "save_yaml", diff --git a/homeassistant/util/yaml/const.py b/homeassistant/util/yaml/const.py index bf1615edb93..9d930b50fd6 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 294cd0ac570..7d713c9f0c0 100644 --- a/homeassistant/util/yaml/loader.py +++ b/homeassistant/util/yaml/loader.py @@ -10,20 +10,9 @@ import yaml 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 @@ DICT_T = TypeVar("DICT_T", bound=Dict) # pylint: disable=invalid-name _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 b90aa3ce936..f88d343bd5c 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 9773824d221..a23f7da4353 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 7dd4924dac8..94365be9a50 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -21,7 +21,6 @@ COMMENT_REQUIREMENTS = ( "blinkt", "bluepy", "bme680", - "credstash", "decora", "decora_wifi", "envirophat", @@ -47,7 +46,7 @@ COMMENT_REQUIREMENTS = ( "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 e28a12acf71..b3a8ca4e486 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 homeassistant.util.yaml import loader as yaml_loader 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 @@ class TestSecrets(unittest.TestCase): "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 008/137] 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 f9a8e3e99b3..19f9da062c9 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 35bce434ccc11b2757bb34efedf9071c5bc9da51 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 25 Feb 2021 21:34:04 +0100 Subject: [PATCH 009/137] 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 623aaf42ca5..e8e9c44ae78 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 e4f84f0c8ef..8f84546371e 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 f88d343bd5c..579f7b3c568 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 a23f7da4353..f92d7060bd1 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 010/137] 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 a82309094e3..e40a9332c38 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 d09cfe754a9..993d466ae18 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -59,7 +59,7 @@ _LOGGER = logging.getLogger(__name__) 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 9621b269081..993f0dba1fd 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 011/137] 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 ecb3fd8c489..580b69251c2 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -18,6 +18,8 @@ from .bridge import authenticate_bridge 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 @@ class HueOptionsFlowHandler(config_entries.OptionsFlow): 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 4fa11f2ad58..593f74331ec 100644 --- a/homeassistant/components/hue/const.py +++ b/homeassistant/components/hue/const.py @@ -12,6 +12,6 @@ CONF_ALLOW_UNREACHABLE = "allow_unreachable" 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 c7dc83183ae..57f4bd7fbca 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 012/137] 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 75bc95b7fe4..93d511875af 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -95,10 +95,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 f2815bec7f6..bff2ecd198c 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 013/137] 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 f5d9461e9e0..9e57a3b72e2 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 579f7b3c568..bfd55ed68bc 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 f92d7060bd1..9dd674925d6 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 73057f3fe21..08b0ffe3080 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 014/137] 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 19f9da062c9..aca184b4606 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 101897c260d6d36399eac13ec6824b487c56bc6b Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Fri, 26 Feb 2021 18:34:40 +0100 Subject: [PATCH 015/137] Add support for v6 features to philips js integration (#46422) --- .../components/philips_js/__init__.py | 54 ++- .../components/philips_js/config_flow.py | 135 ++++-- homeassistant/components/philips_js/const.py | 3 + .../components/philips_js/manifest.json | 2 +- .../components/philips_js/media_player.py | 409 ++++++++++++++---- .../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 | 60 ++- 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, 702 insertions(+), 138 deletions(-) diff --git a/homeassistant/components/philips_js/__init__.py b/homeassistant/components/philips_js/__init__.py index 11e84b6cd82..f3c2eb59789 100644 --- a/homeassistant/components/philips_js/__init__.py +++ b/homeassistant/components/philips_js/__init__.py @@ -8,8 +8,13 @@ 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.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 @@ class PhilipsTVDataUpdateCoordinator(DataUpdateCoordinator[None]): ), ) + 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 523918daa7c..778bcba282b 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 homeassistant.const import ( + CONF_API_VERSION, + CONF_HOST, + CONF_PASSWORD, + CONF_PIN, + CONF_USERNAME, +) -from .const import DOMAIN # pylint:disable=unused-import - -_LOGGER = logging.getLogger(__name__) +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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): } ) + 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 893766b0083..5769a8979ce 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 e41aa348732..e1e1fa69b6b 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 20ef6ed9c0f..2b2714b20ce 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 @@ from homeassistant.components.media_player import ( 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 @@ from homeassistant.components.media_player.const import ( 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 @@ import homeassistant.helpers.config_validation as cv 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_PHILIPS_JS = ( | 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 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): 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 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): 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 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): 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 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): """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 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): 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 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): "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, + ) + + 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=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 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=[ - BrowseMedia( - title=channel, - media_class=MEDIA_CLASS_CHANNEL, - media_content_id=channel, - media_content_type=MEDIA_TYPE_CHANNEL, - can_play=True, - can_expand=False, - ) - for channel in self._channels.values() + 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 2267315501f..df65d453f2b 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 249fe5a892d..b2022a01824 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 bfd55ed68bc..2cfcc67a25d 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 9dd674925d6..de10ccaf6a9 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 1c96a6d4e55..9dea390a600 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 @@ MOCK_SYSTEM = { "model": "modelname", } -MOCK_USERINPUT = { - "host": "1.1.1.1", - "api_version": 1, +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", +} + +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 549ad77fb06..4b6150f9f81 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 75caff78891..45e896319f1 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 43c7c424cf9..ebda40f13e5 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 016/137] 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 b4148801b35..0058374cfe7 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 017/137] 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 593f74331ec..b782ce70193 100644 --- a/homeassistant/components/hue/const.py +++ b/homeassistant/components/hue/const.py @@ -15,3 +15,8 @@ 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" +GROUP_TYPE_LIGHT_SOURCE = "LightSource" diff --git a/homeassistant/components/hue/light.py b/homeassistant/components/hue/light.py index 821d482ec25..6384e47b45e 100644 --- a/homeassistant/components/hue/light.py +++ b/homeassistant/components/hue/light.py @@ -36,7 +36,14 @@ from homeassistant.helpers.update_coordinator import ( ) 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 @@ class HueLight(CoordinatorEntity, LightEntity): @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 @@ class HueLight(CoordinatorEntity, LightEntity): "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 629a9a4c98b..39b9a5a23fc 100644 --- a/tests/components/hue/test_light.py +++ b/tests/components/hue/test_light.py @@ -7,6 +7,12 @@ import aiohue 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 018/137] 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 9d794ace5be..a8bebfbc617 100644 --- a/homeassistant/components/bmw_connected_drive/__init__.py +++ b/homeassistant/components/bmw_connected_drive/__init__.py @@ -122,7 +122,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): 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 7f069e741b8..25adf6cb09f 100644 --- a/homeassistant/components/bmw_connected_drive/device_tracker.py +++ b/homeassistant/components/bmw_connected_drive/device_tracker.py @@ -42,12 +42,12 @@ class BMWDeviceTracker(BMWConnectedDriveBaseEntity, TrackerEntity): @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 c1d90f713f4..bbff139187e 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 2cfcc67a25d..1a6ffa47e8a 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 de10ccaf6a9..3235c215b3d 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 019/137] 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 8f84546371e..10cf300b76b 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 14ebf2708ad..0a5b754dbfc 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 6dbe35760a6..ce7d6b6883d 100755 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ PROJECT_URLS = { 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 020/137] 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 5963e93cf8c..28ccb78d5b9 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 1a6ffa47e8a..e8f09ad002a 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 3235c215b3d..a55805b5365 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 021/137] 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 a40eb10de8b..f5f3d9e5c5b 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 @@ DISCOVERY_SCHEMAS = [ ), ), # 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 022/137] 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 c38869b3e0d..a757947c5cf 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 e8f09ad002a..37eecdba573 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 a55805b5365..91232ad8eaa 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 023/137] 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 418fdf5b26c..5fe91325582 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 00000000000..e858c382221 --- /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 4c83228387d..b5538b8ccad 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 024/137] 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 c68dbe6ee2f..734261d9b08 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 37eecdba573..d0437c6f9bc 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 91232ad8eaa..3eab63288a3 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 025/137] 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 aca184b4606..57308e6eed0 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 807bf15ff35d19599145991b22fe4eb3bc428f4c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 26 Feb 2021 13:28:52 -0800 Subject: [PATCH 026/137] 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 657bc407fb0..c018e07c264 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -26,7 +26,7 @@ from homeassistant.components.media_player.const import ( 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 @@ from . import ( 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 3e498b52a08..91531481a99 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -30,7 +30,12 @@ from homeassistant.exceptions import HomeAssistantError, Unauthorized 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 7c7f83312dd..153f065235c 100644 --- a/tests/components/demo/test_notify.py +++ b/tests/components/demo/test_notify.py @@ -12,7 +12,7 @@ from homeassistant.core import callback 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 @@ CONFIG = {notify.DOMAIN: {"platform": "demo"}} @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 9c8f9a48338..9531602ef0c 100644 --- a/tests/components/google_assistant/test_smart_home.py +++ b/tests/components/google_assistant/test_smart_home.py @@ -30,7 +30,12 @@ from homeassistant.setup import async_setup_component 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 a9b1e9a97fb..ba189020513 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -54,7 +54,7 @@ from homeassistant.util import color 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 30985432718..610bc371b25 100644 --- a/tests/components/homeassistant/test_scene.py +++ b/tests/components/homeassistant/test_scene.py @@ -8,17 +8,14 @@ from homeassistant.components.homeassistant import scene as ha_scene 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 ac51c4e6368..228b5f07837 100644 --- a/tests/components/homekit/conftest.py +++ b/tests/components/homekit/conftest.py @@ -5,7 +5,8 @@ from pyhap.accessory_driver import AccessoryDriver 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 7e7bd068842..51659cf7736 100644 --- a/tests/components/shelly/conftest.py +++ b/tests/components/shelly/conftest.py @@ -10,10 +10,14 @@ from homeassistant.components.shelly.const import ( 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 027/137] 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 9d1c333b201..4fda656e7b4 100644 --- a/homeassistant/components/shelly/const.py +++ b/homeassistant/components/shelly/const.py @@ -74,5 +74,5 @@ INPUTS_EVENTS_SUBTYPES = { # 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 848ef990340..0379bfec1cf 100644 --- a/homeassistant/components/shelly/light.py +++ b/homeassistant/components/shelly/light.py @@ -28,20 +28,13 @@ from .const import ( 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 @@ class ShellyLight(ShellyBlockEntity, LightEntity): 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 @@ class ShellyLight(ShellyBlockEntity, LightEntity): 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 @@ class ShellyLight(ShellyBlockEntity, LightEntity): 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 @@ class ShellyLight(ShellyBlockEntity, LightEntity): 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 @@ class ShellyLight(ShellyBlockEntity, LightEntity): 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 028/137] 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 330ff81d1d2..57b87b18320 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 029/137] 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 10a72a85dff..949af7dbb32 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 030/137] 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 e8e9c44ae78..01f1c72f8d6 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 10cf300b76b..9506171303b 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 d0437c6f9bc..e6fb8b88e4e 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 3eab63288a3..e3fc888d43f 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 031/137] 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 9d65bb4c5d4..81910db38d6 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -27,7 +27,7 @@ UPDATER_URL = "https://updater.home-assistant.io/" 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 032/137] 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 8969072553c..d6e2ee0fc65 100644 --- a/homeassistant/components/mqtt/device_trigger.py +++ b/homeassistant/components/mqtt/device_trigger.py @@ -13,6 +13,7 @@ from homeassistant.const import ( 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 @@ TRIGGER_DISCOVERY_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( { 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 @@ class TriggerInstance: } 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 @@ class Trigger: 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 async_setup_trigger(hass, config, config_entry, discovery_data): 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 459adabd418..82f7885b85d 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 f200de6a274..210dac19e0c 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 23078b9ba23..d0a86e08655 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 033/137] 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 5fe91325582..74aa05e58f3 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 034/137] 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 ad2bf5f17c5..d7bb0dbe5bc 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 e6fb8b88e4e..6dd457f792f 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 e3fc888d43f..1ae58db2865 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 035/137] 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 acc61408a48..4d1598c728c 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 6dd457f792f..010cb3d49e1 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 1ae58db2865..5e8df0c89fb 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 036/137] 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 57308e6eed0..c6cccb49d43 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 8513250628a61b387304c7682d664200fff757bf Mon Sep 17 00:00:00 2001 From: AJ Schmidt Date: Sun, 28 Feb 2021 18:21:04 -0500 Subject: [PATCH 037/137] 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 1697858718d..c3e72e407c2 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 010cb3d49e1..05bc4f01b79 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 5e8df0c89fb..78cb4d8f8ce 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 038/137] Fix Xiaomi Miio discovery (#47134) --- .../components/xiaomi_miio/config_flow.py | 17 +++++++---- .../components/xiaomi_miio/strings.json | 28 +++++++++---------- .../xiaomi_miio/test_config_flow.py | 14 ++++------ 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/config_flow.py b/homeassistant/components/xiaomi_miio/config_flow.py index d7e2198f72f..2e069b30da3 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 @@ from .device import ConnectXiaomiDevice _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 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) # 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 90710baebca..e3d9376bc31 100644 --- a/homeassistant/components/xiaomi_miio/strings.json +++ b/homeassistant/components/xiaomi_miio/strings.json @@ -1,24 +1,24 @@ { "config": { - "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)" - } - } + "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." }, - "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", - "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]" + "flow_title": "Xiaomi Miio: {name}", + "step": { + "device": { + "data": { + "host": "[%key:common::config_flow::data::ip%]", + "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" + } } } } diff --git a/tests/components/xiaomi_miio/test_config_flow.py b/tests/components/xiaomi_miio/test_config_flow.py index f4f7b5e2b46..f53fe6e40b4 100644 --- a/tests/components/xiaomi_miio/test_config_flow.py +++ b/tests/components/xiaomi_miio/test_config_flow.py @@ -6,10 +6,7 @@ from miio import DeviceException 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_TOKEN = "12345678901234567890123456789012" 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 039/137] 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 b18bafb0bbf..de5199ccd4c 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 05bc4f01b79..1418ebb2d7b 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 78cb4d8f8ce..c5f401d2eca 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 040/137] 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 534ea3c6f95..c042872f4cd 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -491,8 +491,11 @@ class HomeKit: # 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 ec324602684..9ce3e96f06f 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 041/137] 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 4d1598c728c..ac3fb0251e2 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 1418ebb2d7b..48d2374c034 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 c5f401d2eca..09eacd9b78a 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 042/137] 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 e858c382221..4c83228387d 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 043/137] 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 c6cccb49d43..86abfa635f0 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 819738a15c5b197ad661c46db5d6d0bfbf8370ba Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Tue, 2 Mar 2021 02:12:49 +0100 Subject: [PATCH 044/137] 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 6ed0286e184..d9c31210bea 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 @@ from .entity import ZWaveBaseEntity 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 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): # 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 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): ), ) 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 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): """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 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): 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 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): 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 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): @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 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): 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 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): 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 ca36ea35393..8991776fed6 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 045/137] 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 312e726b91d..80d05ae1b9c 100644 --- a/homeassistant/components/github/sensor.py +++ b/homeassistant/components/github/sensor.py @@ -228,18 +228,25 @@ class GitHubData: 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 046/137] 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 dbc0dd01454..b54300faaa7 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -69,8 +69,6 @@ ENTRY_STATE_SETUP_RETRY = "setup_retry" 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 @@ class ConfigEntries: 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 047/137] 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 8c1d137bc85..340596ff1ef 100644 --- a/homeassistant/components/harmony/data.py +++ b/homeassistant/components/harmony/data.py @@ -22,17 +22,8 @@ class HarmonyData(HarmonySubscriberMixin): 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 @@ class HarmonyData(HarmonySubscriberMixin): 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 @@ class HarmonyData(HarmonySubscriberMixin): except aioexc.TimeOut: _LOGGER.warning("%s: Connection timed-out", self._name) return False + return True async def shutdown(self): @@ -159,10 +163,12 @@ class HarmonyData(HarmonySubscriberMixin): ) 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 d3bed33d560..b2652cc43d1 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 @@ class HarmonySubscriberMixin: 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 @@ class HarmonySubscriberMixin: 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 @@ class HarmonySubscriberMixin: 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 048/137] 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 5f08f79dc00..589cc97baea 100644 --- a/homeassistant/components/persistent_notification/__init__.py +++ b/homeassistant/components/persistent_notification/__init__.py @@ -11,6 +11,7 @@ from homeassistant.core import HomeAssistant, callback 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 @@ SERVICE_MARK_READ = "mark_read" 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 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool: 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 049/137] 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 2e069b30da3..d6ee83e9842 100644 --- a/homeassistant/components/xiaomi_miio/config_flow.py +++ b/homeassistant/components/xiaomi_miio/config_flow.py @@ -125,7 +125,9 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 050/137] 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 d4423dc3a88..ccb52127525 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -108,6 +108,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): "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 051/137] 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 9e6ee086c73..1f378c59335 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 3ba79e85bf2..55311005201 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 052/137] 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 de5199ccd4c..6122428ea98 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 48d2374c034..5ab088e5788 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 09eacd9b78a..fbc39974f40 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 053/137] 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 57b87b18320..edda379aedc 100644 --- a/homeassistant/components/lutron_caseta/fan.py +++ b/homeassistant/components/lutron_caseta/fan.py @@ -46,6 +46,8 @@ class LutronCasetaFan(LutronCasetaDevice, FanEntity): """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 054/137] 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 93d511875af..798fd9fda2c 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 ebba16136a0..a5ee628754e 100644 --- a/tests/components/zwave_js/common.py +++ b/tests/components/zwave_js/common.py @@ -16,3 +16,6 @@ PROPERTY_DOOR_STATUS_BINARY_SENSOR = ( 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 e0bc588abf4..72835fb17c1 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -10,9 +10,7 @@ from zwave_js_server.model.driver import Driver 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 @@ from tests.common import MockConfigEntry, load_fixture @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 bff2ecd198c..2a2f249c361 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -18,7 +18,11 @@ from homeassistant.config_entries import ( 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 8991776fed6..c16e2474980 100644 --- a/tests/components/zwave_js/test_light.py +++ b/tests/components/zwave_js/test_light.py @@ -12,9 +12,11 @@ from homeassistant.components.light import ( ) 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 db815506a6b..b11d2bfd180 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 055/137] 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 01f1c72f8d6..e8f9ff2698d 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 9506171303b..cb211fb1962 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 5ab088e5788..e27b43f94a8 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 fbc39974f40..400cc372b2b 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 056/137] 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 81910db38d6..9d65bb4c5d4 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -27,7 +27,7 @@ UPDATER_URL = "https://updater.home-assistant.io/" 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 057/137] 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 86abfa635f0..2edbfa33a10 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 ab5173c4cf07c7f2b9ec64241137c060e4711154 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Thu, 25 Feb 2021 00:05:20 +0000 Subject: [PATCH 058/137] [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 5607b426cc9..03465d5c53d 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 1128a820cd5..9d1e76aaf2b 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 1697f634d9a..e48d27801cc 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 4067bf75f83..f1f1ce7888b 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 3903ea77c34..1d1df915630 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 dc6a8414bce..5e9a8e4493f 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 496a21339cb..d9eb14b1a62 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 8010dfc2e78..b5d8c593ea9 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 01ff745eed3..c09b7a17635 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 c50c270b74c..6f5f2d276ff 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 e6c4067d8ac..cdc37fc27f7 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 af652c54509..1c5327dc662 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 2f3a7313f75..7f85335d7bb 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 00000000000..b62fc7023a4 --- /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 00000000000..23afb6a3d90 --- /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 00000000000..ed3ead421e1 --- /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 00000000000..3722c258afa --- /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 00000000000..8fd3f7b7122 --- /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 00000000000..488a43ae24e --- /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 00000000000..64845ff7697 --- /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 00000000000..2cce63d95ea --- /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 69d52436beb..e4cf54eb365 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 00000000000..79aaec23123 --- /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 48e9e1c8993..e78b15c68cb 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 00000000000..996a22c8422 --- /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 00000000000..3dbc55f5b1b --- /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 7289d53e71f..3a6ba936dee 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 af1ceba2c97..be485577e65 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 00000000000..7cb0141826a --- /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 c63a59e18e7..0ff044abc5e 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 71a80dbd577..9bfe2ef6be6 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 86958670d70..4d9b5a397dd 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 41b27cc387f..25a81209607 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 0870b05a6d1..dbd83622d8a 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 e9aaeb60df8..3b0129567c4 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 37bff5f9b70..8c24d2d2251 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 a0f10c9684a..4721514e615 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 9a64def4156..4748fe63af2 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 2679a4de20a..ef35ff667c4 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 84346aed2ef..d00744e4cb4 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 605263c4489..95a0782cf12 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 dd51fdc1bc5..d420093996c 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 97673069054..b150c8d5b16 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 e7da47dd658..82006860db3 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 d2f73fca37b..e4f7d4a8102 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 f422e2641f6..c3c08575052 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 00000000000..45620fe7795 --- /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 00000000000..25dab56796c --- /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 00000000000..39e2a56dc4d --- /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 00000000000..455ba7fdc0c --- /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 00000000000..26ccd333546 --- /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 00000000000..20e5d68288d --- /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 00000000000..c90e6956301 --- /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 00000000000..de4ea12cb6f --- /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 00000000000..8a268f3db49 --- /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 00000000000..aa84ec33d8c --- /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 00000000000..4ea7b2401c3 --- /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 00000000000..8a08a06c699 --- /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 e02378432ab..16cbbc77277 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 772a67c118e..dea33946af5 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 86f1e656e51..3198bfb4192 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 00000000000..f81781cbc0f --- /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 45664554aed..fcfa89ef082 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 00000000000..671d18a2cd3 --- /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 00000000000..1a8b10de809 --- /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 00000000000..aa4d80ac71d --- /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 00000000000..d33f2640445 --- /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 00000000000..ff34530e4a9 --- /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 00000000000..0f3ddabfc4f --- /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 99e062b3842..8725eb48016 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 fe8fc74d273..6c294d467ab 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 eab1d9741ad..431f105df3d 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 c9be7e60825..b25e0843967 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 94dd5b3fb0f..69646be2292 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 208299fd442..0146996dce5 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 f5f1bfd39ed..89d58d14292 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 7f0f85b7326..8410cabbbb9 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 46fc915d7bd..38ef34d5afc 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 00000000000..2a1fb9c8bb8 --- /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 72f9ff82ba4..8c15279dca8 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 0569c39321a..8ad15260b0d 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 96c10f3e2f0..3acfdbdf074 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 ebcc16dafac..10a4fe2efab 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 75dd593982a..ddcc54d239f 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 423dbb6a2bb..b7f077f2c73 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 0c0c184b5fe..1fe99195f7a 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 f0b4ddf59a9..bc7683ac0a4 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 00000000000..a6bf6902aab --- /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 349b4b2818e..b1beb4058bc 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 c160ac631ee..236d250122a 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 cf77b94d025..a0cda915172 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 6526d0a9800..40ca767b4ac 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 1f4fb5490d1..94d8e3ac01e 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 e80f86696fc..9c98d6ad1e7 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 530d632040c..ff2ca2351e6 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 3375cac31d5..0a6cf433d87 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 50eb4d16887..80528b71370 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 6806b5072c1..731c0bbcea8 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 d51507b616f..4c68e63530f 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 040e3997ac1..9cc8bf822b8 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 b724fb34e48..acd049fc561 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 b139b0dacc6..2bfd994132b 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 5b7e5f47017..1a65ce3ea71 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 b9ff1b41920..f1495b1aeda 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 059/137] [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 5e45098c11d..d6799ba6e37 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 677c7e80236..13135dbf53e 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 00000000000..1ae29deb08c --- /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 00000000000..4c4d8fcc9bb --- /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 00000000000..76eaf50b932 --- /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 00000000000..60e4aed57a2 --- /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 00000000000..94eca99dda3 --- /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 00000000000..75b52558374 --- /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 00000000000..d68810fc957 --- /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 00000000000..f2585bb790f --- /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 faf1b1d74fc..cdfaed9183c 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 00000000000..0f02cd974c2 --- /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 00000000000..b0641022bf0 --- /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 72fc9ce7389..868ae0d770e 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 00000000000..0f02cd974c2 --- /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 00000000000..d6a17561c3d --- /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 00000000000..d78c36b72d7 --- /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 556fe2626d4..b1159c1dd9d 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 e396deabb68..e62836f9a7e 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 00000000000..bc74ecfd7ea --- /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 7084a972e29..c486b9c6bfe 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 5a27562df71..ba1261c1100 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 f8f122e63e2..69cd1b1623a 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 00000000000..ee3bf7347ca --- /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 00000000000..deccc23c75d --- /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 60e2196b387..74dece0c54e 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 48af1bed0f4..090d9271dee 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 00000000000..da03cc43b4b --- /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 fd4b8c36a8b..60a989ade0d 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 32d7a6d2e6d..26fd155a0ad 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 060/137] [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 ff0d81f94d3..4bf5f9fce45 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 1af1e8d803c..1ea9cb98b56 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 aef6a717895..07dc59e1a56 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 d3ac1ab6b49..e22ad82e1fd 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 b5d8c593ea9..a76c7a69d7f 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 7f85335d7bb..d2db5476555 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 00000000000..cc7df4f8ab3 --- /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 64845ff7697..af07ce716d0 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 679d9360a82..8b1ca3c3500 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 00000000000..e1bf6ad0646 --- /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 00000000000..c481f90bf75 --- /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 ad8e894d093..5418735ec07 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 0569c39321a..8ad15260b0d 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 9a85d1e6e9f..fee64457652 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 bcf61fe9868..9013723ac6c 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 e4f7d4a8102..98a27fb1139 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 00000000000..e9356485e08 --- /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 8eb4a39cfb6..57476791b8f 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 00000000000..5b3dc46753d --- /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 00000000000..843262aa318 --- /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 37c1d6b4d9e..6ad22742533 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 a0ab0e497da..3b3ebf9fe3b 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 00000000000..47cd8290f21 --- /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 46c2d7d2721..152f7d47597 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 431f105df3d..0bdc3170a5a 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 387dbe7b26c..9e3e24d5771 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 b2022a01824..65d4f417b9f 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 c77ef726411..9953df9c272 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 02411ea999f..119e4c641af 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 34bcb4ab98a..97d0d454a4f 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 37437dfe836..64b7f1b73f7 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 b285b288525..d3196c591cb 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 a63320919c6..b0ae5c9d3ad 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 bdc86919f74..46b18857fe8 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 00000000000..6dbb0702f46 --- /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 b1beb4058bc..799e19ea371 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 2a12d00f57d..18ecf648310 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 9179418def9..9e11dbad82b 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 da03cc43b4b..ae205d79aef 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 68202e1631e..aa48ba7cfa8 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 3ea12e3a465..66209e61ee6 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 f4f071d9097..8aed5085391 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 5f0868a3f74..abe0ab066fb 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 7f46c02ece5..c15cfd26f31 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 061/137] [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 af07ce716d0..d59f5590518 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 00000000000..e7e600f7f07 --- /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 c481f90bf75..5a5aac723ad 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 455ba7fdc0c..89459d1829f 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 26ccd333546..d3206ca2897 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 a6b8b5c2b82..809223a04ae 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 505a6472ea8..980bb6800e1 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 9ae65c18fa4..25c28edcf1d 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 dadf15fb67a..a9c647a644b 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 9306ecf7a29..83511ff246a 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 a6bf6902aab..25544534297 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 f1a263d5cb4..25b0f7bec29 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 40ca767b4ac..b46bf127963 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 10ce9972818..30def127e7a 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 acd049fc561..f893d2d7684 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 062/137] [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 00000000000..d7254aea92f --- /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 95400de23b4..e76cec94f4c 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 720f68f8349..0a5426c87d8 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 8cbfd138257..c5c9359c67f 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 b1469af787e..41ca90a8c02 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 4d60b158c4c..19ef2ae7532 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 00000000000..433bf17b814 --- /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 11fe9ff88b3..0bd7749c65f 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 3d05dc82abc..d2c42bf5531 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 1c5327dc662..8bb8e178869 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 00000000000..f18197e1cca --- /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 00000000000..81a4b5c1fce --- /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 1be70def034..d9a05269748 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 00000000000..854d61f1790 --- /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 00000000000..72b837c862c --- /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 00000000000..af8d410eb18 --- /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 af3cba40dc6..8e5f4d9f20d 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 8e79076bda6..16263722482 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 00000000000..04f985946fb --- /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 87ad743dca5..6acebca0ca4 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 00000000000..ca3f90536d4 --- /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 ed003131bf2..bb8eacd5376 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 815edb1fb27..f310babfd80 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 00000000000..71ce0154639 --- /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 00000000000..625c7372347 --- /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 00000000000..492314e5cc6 --- /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 00000000000..a06c89f1d2a --- /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 00000000000..0eee2778d05 --- /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 13f8c6bd800..b6aacf2d0ef 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 00000000000..5bab6ed132b --- /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 00000000000..4e23becb8af --- /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 a7f25fa9d7c..4909c85d053 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 9947b5ac990..27f4d0e5d7f 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 00000000000..625c7372347 --- /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 00000000000..7f60f15d598 --- /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 00000000000..189226f29d5 --- /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 00000000000..54bef84c30a --- /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 361b4789328..0d00cebb6a5 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 00000000000..30d7e6865cd --- /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 662d7ed6ed9..4bf21552952 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 00000000000..f59a17bce49 --- /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 00000000000..04648fe5845 --- /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 af161b6b16b..13bfd52e980 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 5171baab654..eaf68b507f9 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 2890c5c31c6..26d7b728771 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 137b953a145..7f19fa0d035 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 710d363f771..2bb1a8fc239 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 c30286d8744..0ccd42c812b 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 4a937029296..8811b870316 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 00000000000..67b8ed59e0b --- /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 4bfb3c7503d..152161cb27f 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 ad5323d3957..27064ed1055 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 04cd0d70218..18d6076898d 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 d9a17e46058..88360c75678 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 00000000000..fbb3411a6c5 --- /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 00000000000..1c162d61e99 --- /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 558209af411..2fd964fe013 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 ce10615a289..e4d0ec10e23 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 530fef95af2..3fb5bb8f3e1 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 0fc1297ce7c..48161f552b8 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 be38ddf1a4d..05dd66fe56c 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 b266e25b39c..88180f9bacf 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 257ed829b6a..5f21dd0c2b6 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 d56a81e14d4..7cf11a1085e 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 951ae546b56..3d893ade2f0 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 dce2002faa9..3b0a89b7485 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 d4903bc8c6d..9ff130605ef 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 063/137] 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 b54300faaa7..b0ec71be9cf 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -257,12 +257,19 @@ class ConfigEntry: 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 064/137] 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 ecbcd8563a7..f5d425cb9ef 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 6978f09ab68..c8029c2e313 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 65d8d21fc0c..c1a01004fe9 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 b16eace14fd..5867d0d6b51 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 01e0275feeb..4f9f6514531 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 99902b8dd36..aa9519fd68b 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 f039a14d5b3..300c3ddd1db 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 cb211fb1962..5aaa5b1b469 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 e27b43f94a8..d63ab3c5b83 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 400cc372b2b..ef6c0706c87 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 065/137] 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 fdeb10bcafe..5b40d7142f1 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 798fd9fda2c..c19b1b355a4 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 import device_registry, entity_registry 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 @@ from .const import ( 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 .discovery import async_discover_values 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 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: }, ) + 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 async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) 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 00000000000..54169dcaf94 --- /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 cc19fb85d3a..37923c574b4 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -9,33 +9,25 @@ import voluptuous as vol 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 19e6fc3db14..e3f20366ab0 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 @@ DATA_UNSUBSCRIBE = "unsubs" 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 5d3aa730a7c..eb13ad512e3 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 5be980d52cb..101942dc717 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 72835fb17c1..50cacd97422 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -14,6 +14,124 @@ from homeassistant.helpers.device_registry import async_get as async_get_device_ 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 08b0ffe3080..fc97f7420cf 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 2a2f249c361..6f60bbc0300 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.const import DOMAIN 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 066/137] 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 d66906810b2..c34dc34f0dd 100644 --- a/homeassistant/components/alexa/state_report.py +++ b/homeassistant/components/alexa/state_report.py @@ -73,10 +73,7 @@ async def async_enable_proactive_mode(hass, smart_home_config): 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_enable_proactive_mode(hass, smart_home_config): 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 a057eada531..2cbf8636d79 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 067/137] 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 c19b1b355a4..d4e349645cf 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -28,10 +28,13 @@ from .const import ( 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 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 e3f20366ab0..ffd6031349a 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -34,12 +34,15 @@ ATTR_HOME_ID = "home_id" 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 2a347f6afea..e40782270a9 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 068/137] 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 969eb254072..aa24f81eb69 100644 --- a/homeassistant/components/mqtt/number.py +++ b/homeassistant/components/mqtt/number.py @@ -28,6 +28,7 @@ from . import ( 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 @@ class MqttNumber(MqttEntity, NumberEntity, RestoreEntity): 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 069/137] 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 6122428ea98..45deb938b59 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 d63ab3c5b83..a2d1c502081 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 ef6c0706c87..5e65e950654 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 070/137] 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 91026c40c2f..a53f7f9fb08 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -352,6 +352,9 @@ class NetatmoThermostat(NetatmoBase, ClimateEntity): 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 071/137] 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 e8f9ff2698d..e7d7723a510 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 5aaa5b1b469..44e6195317d 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 a2d1c502081..c4962d24400 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 5e65e950654..ed722a07256 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 072/137] 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 d7bb0dbe5bc..7d367c3dc00 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 c4962d24400..e97062e4c11 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 ed722a07256..2c9ac79095d 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 073/137] 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 da3282108a5..c77bbfbd50a 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 @@ class ClimaCellWeatherEntity(ClimaCellEntity, WeatherEntity): # 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 @@ class ClimaCellWeatherEntity(ClimaCellEntity, WeatherEntity): 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 074/137] 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 9e57a3b72e2..c812515a179 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 e97062e4c11..aa5c9d2bb85 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 2c9ac79095d..28b2d0ca08c 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 075/137] 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 2edbfa33a10..47fb090305d 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 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 076/137] 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 d6bf0ec4e12..b6e70ab56e8 100644 --- a/homeassistant/components/climacell/__init__.py +++ b/homeassistant/components/climacell/__init__.py @@ -256,7 +256,8 @@ class ClimaCellEntity(CoordinatorEntity): """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 077/137] 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 e7d7723a510..4d4127fc2f2 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 44e6195317d..752a3755169 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 aa5c9d2bb85..ffbd439ee2e 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 28b2d0ca08c..318b04e5e70 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 a89ba0ed8e0a86ef61a8fce3f839d2c834389c34 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 3 Mar 2021 19:12:37 +0100 Subject: [PATCH 078/137] 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 b0ec71be9cf..12a795d0a51 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -11,10 +11,9 @@ import weakref 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 @@ class ConfigEntries: 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 @@ class EntityRegistryDisabledHandler: @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 47fb090305d..2a9bd69c761 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -202,7 +202,6 @@ CONF_ZONE = "zone" # #### 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 705f6cdd89a..d311538f27f 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -6,10 +6,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Union, 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 @@ from .typing import UNDEFINED, HomeAssistantType, UndefinedType # 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 @@ class DeviceRegistry: 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 @@ class DeviceRegistry: 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 c86bd64d73e..8a7a4de970a 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -31,7 +31,6 @@ from homeassistant.const import ( ATTR_RESTORED, ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT, - EVENT_CONFIG_ENTRY_DISABLED_BY_UPDATED, EVENT_HOMEASSISTANT_START, STATE_UNAVAILABLE, ) @@ -158,10 +157,6 @@ class EntityRegistry: 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 @@ class EntityRegistry: 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 @@ class EntityRegistry: """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 @@ class EntityRegistry: ("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 @@ class EntityRegistry: 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 @@ class EntityRegistry: 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 86cdab82238..0a1a27efef5 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 079/137] 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 5128a49d8b7..2650bd61bfb 100644 --- a/homeassistant/components/switch/light.py +++ b/homeassistant/components/switch/light.py @@ -12,7 +12,7 @@ from homeassistant.const import ( 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 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( 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 @@ class LightSwitch(LightEntity): 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 @@ class LightSwitch(LightEntity): @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 @@ class LightSwitch(LightEntity): 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 080/137] 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 2a9bd69c761..ec2ab3bff0c 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 c2f7a38d09152577ebc61462b0e51ba8d04be963 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 4 Mar 2021 21:53:09 +0100 Subject: [PATCH 081/137] 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 a8b32a31576..9c5f72d5877 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -23,7 +23,7 @@ from .gateway import ConnectXiaomiGateway _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 d6c39146f6a..4fa575118f8 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -21,7 +21,6 @@ MODELS_SWITCH = [ "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 3cc95572e6c..e523dfb0bb7 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -22,6 +22,7 @@ import homeassistant.helpers.config_validation as cv 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 082/137] 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 d4e349645cf..6bd951fdf5e 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 54169dcaf94..b8d020cfb00 100644 --- a/homeassistant/components/zwave_js/addon.py +++ b/homeassistant/components/zwave_js/addon.py @@ -67,8 +67,8 @@ class AddonManager: """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 @@ class AddonManager: task and not task.done() for task in ( self._install_task, - self._setup_task, + self._start_task, self._update_task, ) ) @@ -125,8 +125,21 @@ class AddonManager: 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 @@ class AddonManager: 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 @@ class AddonManager: 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 @@ class AddonManager: 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 @@ class AddonManager: """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 @@ class AddonManager: 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 @@ class AddonManager: ) @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 @@ class AddonManager: 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 37923c574b4..35fc4417d13 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -172,11 +172,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """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 fc97f7420cf..a9aa837aac1 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 083/137] 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 5fbdf499146..7062267de19 100644 --- a/homeassistant/components/generic_thermostat/climate.py +++ b/homeassistant/components/generic_thermostat/climate.py @@ -35,6 +35,7 @@ from homeassistant.const import ( 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 @@ class GenericThermostat(ClimateEntity, RestoreEntity): 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 084/137] 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 35fc4417d13..4929f7e7869 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -117,7 +117,15 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 a9aa837aac1..7eea126e52e 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 085/137] 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 40087650141..c592681a015 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 831e20b78a1..9eb9ac79a94 100644 --- a/tests/components/homeassistant/triggers/test_numeric_state.py +++ b/tests/components/homeassistant/triggers/test_numeric_state.py @@ -10,12 +10,7 @@ import homeassistant.components.automation as automation 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 5074b6e70c4..a041dc7fc7f 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 086/137] 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 54966538aae..cc449b89e91 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -118,7 +118,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): 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 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): @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 a5ee628754e..ec54e139404 100644 --- a/tests/components/zwave_js/common.py +++ b/tests/components/zwave_js/common.py @@ -16,6 +16,7 @@ PROPERTY_DOOR_STATUS_BINARY_SENSOR = ( 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 50cacd97422..aa9da282635 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 44804825885..ea75f10328c 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -31,6 +31,7 @@ from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE 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 00000000000..a2fdaa99561 --- /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 087/137] 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 4d4127fc2f2..a5b4c6f10d5 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 752a3755169..919b53f64ed 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 ffbd439ee2e..1b7723629e6 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 318b04e5e70..14c12395170 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 088/137] 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 df737f101ba..db380ae11ca 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.core import HomeAssistant, callback 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_setup_entry(hass: HomeAssistant, entry: ConfigEntry): 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 @@ class WemoDiscovery: 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 7c2b43dfd8c..374222d8688 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 089/137] 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 3cff3beed3c..68783c3426a 100644 --- a/homeassistant/components/rflink/__init__.py +++ b/homeassistant/components/rflink/__init__.py @@ -203,25 +203,28 @@ async def async_setup(hass, config): # 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 090/137] 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 18f46b3d619..1fbf35b1603 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -77,6 +77,7 @@ _NOT_SPEED_INTERVAL = "interval" _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_SPEEDS_FILTER = { _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 091/137] 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 821fe164ea9..a47b32fc6d1 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -22,7 +22,6 @@ from homeassistant.const import ( DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, - LIGHT_LUX, PERCENTAGE, PRESSURE_HPA, TEMP_CELSIUS, @@ -38,6 +37,7 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "Xiaomi Miio Sensor" DATA_KEY = "sensor.xiaomi_miio" +UNIT_LUMEN = "lm" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -302,7 +302,7 @@ class XiaomiGatewayIlluminanceSensor(Entity): @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 092/137] 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 c77bbfbd50a..c72220d8856 100644 --- a/homeassistant/components/climacell/weather.py +++ b/homeassistant/components/climacell/weather.py @@ -260,6 +260,7 @@ class ClimaCellWeatherEntity(ClimaCellEntity, WeatherEntity): 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 093/137] 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 3a88243b0b9..f82bdcd96b8 100644 --- a/homeassistant/components/airvisual/__init__.py +++ b/homeassistant/components/airvisual/__init__.py @@ -39,6 +39,7 @@ from .const import ( 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 094/137] 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 c72220d8856..e5a24197d6b 100644 --- a/homeassistant/components/climacell/weather.py +++ b/homeassistant/components/climacell/weather.py @@ -22,7 +22,6 @@ from homeassistant.const import ( LENGTH_MILES, PRESSURE_HPA, PRESSURE_INHG, - TEMP_CELSIUS, TEMP_FAHRENHEIT, ) from homeassistant.helpers.entity import Entity @@ -31,7 +30,6 @@ 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 ( @@ -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 095/137] 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 6bd951fdf5e..0da394721ac 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -48,7 +48,8 @@ from .const import ( 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 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 cc449b89e91..325cf14b379 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -125,18 +125,10 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): ) 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 d0ed9eb5291..c061abc4d0d 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -169,7 +169,6 @@ class ZWaveBaseEntity(Entity): 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 @@ class ZWaveBaseEntity(Entity): 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 @@ class ZWaveBaseEntity(Entity): 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 9582b7ee054..16baeb816c2 100644 --- a/homeassistant/components/zwave_js/helpers.py +++ b/homeassistant/components/zwave_js/helpers.py @@ -3,7 +3,6 @@ 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 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 homeassistant.helpers.entity_registry import async_get as async_get_ent_reg 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 d9c31210bea..b501ecb58e7 100644 --- a/homeassistant/components/zwave_js/light.py +++ b/homeassistant/components/zwave_js/light.py @@ -228,7 +228,6 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): "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 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): "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 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): "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 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): "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 c812515a179..2a6f036fa80 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 00000000000..49c18073de5 --- /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 1b7723629e6..5d69ddd65bd 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 14c12395170..5fdf5100605 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 dcbd924c86e..1b3f29e9cb1 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 ea75f10328c..fe3e0708acc 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 6f60bbc0300..cd2017f2c66 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 90410998597..8574674714f 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 0dc040c6cb2..b26b69be9ad 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 096/137] 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 4fa575118f8..b2b63344c17 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -22,7 +22,7 @@ MODELS_SWITCH = [ "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 097/137] 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 ec2ab3bff0c..e454f3ab09d 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 d9542c2efe54f72f690e2f8bfe00e6f405c6dd52 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 5 Mar 2021 01:38:33 +0100 Subject: [PATCH 098/137] 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 b8d020cfb00..818e46a34aa 100644 --- a/homeassistant/components/zwave_js/addon.py +++ b/homeassistant/components/zwave_js/addon.py @@ -173,6 +173,7 @@ class AddonManager: if not update_available: return + await self.async_create_snapshot() await async_update_addon(self._hass, ADDON_SLUG) @callback @@ -184,7 +185,6 @@ class AddonManager: 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 cd2017f2c66..e56db58f3cc 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 9fa0de86005716817931482dcd12cf53433bd0c5 Mon Sep 17 00:00:00 2001 From: mvn23 Date: Fri, 5 Mar 2021 20:22:40 +0100 Subject: [PATCH 099/137] 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 066cee61c05..baa02dc3f46 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 5d69ddd65bd..86a8a705a5f 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 5fdf5100605..fb684893daf 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 100/137] 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 6e55b884d4d..5163c9582b0 100644 --- a/homeassistant/components/netatmo/camera.py +++ b/homeassistant/components/netatmo/camera.py @@ -7,6 +7,7 @@ import voluptuous as vol 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 async_setup_entry(hass, entry, async_add_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 a53f7f9fb08..d12ee9263db 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -25,6 +25,7 @@ from homeassistant.const import ( 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 @@ NETATMO_MAP_PRESET = { 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 async_setup_entry(hass, entry, async_add_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 9bc4b197f1b..be0120bd1a0 100644 --- a/homeassistant/components/netatmo/data_handler.py +++ b/homeassistant/components/netatmo/data_handler.py @@ -129,7 +129,11 @@ class NetatmoDataHandler: 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 dc8bf3f1fc8..eed12f048c8 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 f8484444818..9176b670bea 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -20,6 +20,7 @@ from homeassistant.const import ( 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 async_setup_entry(hass, entry, async_add_entities): 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 101/137] 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 201e9f3a546..dc9b56fcdfe 100644 --- a/homeassistant/components/hue/bridge.py +++ b/homeassistant/components/hue/bridge.py @@ -19,7 +19,6 @@ from .const import ( CONF_ALLOW_UNREACHABLE, DEFAULT_ALLOW_HUE_GROUPS, DEFAULT_ALLOW_UNREACHABLE, - DEFAULT_SCENE_TRANSITION, LOGGER, ) from .errors import AuthenticationRequired, CannotConnect @@ -34,9 +33,7 @@ SCENE_SCHEMA = vol.Schema( { 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 @@ class HueBridge: """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 b782ce70193..8d01617073b 100644 --- a/homeassistant/components/hue/const.py +++ b/homeassistant/components/hue/const.py @@ -14,8 +14,6 @@ DEFAULT_ALLOW_UNREACHABLE = False 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 29bc2acf03a..093f6356b09 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 102/137] 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 e1e1fa69b6b..9ed1cedbf05 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 86a8a705a5f..ba0cb7e2314 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 fb684893daf..1e2c7ff0fb1 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 103/137] 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 a5b4c6f10d5..8093c65d91a 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 919b53f64ed..0586b956f39 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 ba0cb7e2314..def728244f5 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 1e2c7ff0fb1..58885bf07e7 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 104/137] Use conn_made callback in MySensors (#47463) --- homeassistant/components/mysensors/const.py | 1 - homeassistant/components/mysensors/gateway.py | 40 ++++++++----------- homeassistant/components/mysensors/handler.py | 23 +---------- 3 files changed, 17 insertions(+), 47 deletions(-) diff --git a/homeassistant/components/mysensors/const.py b/homeassistant/components/mysensors/const.py index 66bee128d4d..9116009d7b1 100644 --- a/homeassistant/components/mysensors/const.py +++ b/homeassistant/components/mysensors/const.py @@ -29,7 +29,6 @@ CONF_GATEWAY_TYPE_ALL: List[str] = [ 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 4267ba5cbb3..b6797cafb37 100644 --- a/homeassistant/components/mysensors/gateway.py +++ b/homeassistant/components/mysensors/gateway.py @@ -26,7 +26,6 @@ from .const import ( CONF_TOPIC_OUT_PREFIX, CONF_VERSION, DOMAIN, - MYSENSORS_GATEWAY_READY, MYSENSORS_GATEWAY_START_TASK, MYSENSORS_GATEWAYS, GatewayId, @@ -36,7 +35,7 @@ from .helpers import discover_mysensors_platform, validate_child, validate_node _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() + gateway_ready = asyncio.Event() - 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) + 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 @@ async def try_connect(hass: HomeAssistantType, user_input: Dict[str, str]) -> bo ) 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 _gw_start( 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 10165a171e0..a47c9174b23 100644 --- a/homeassistant/components/mysensors/handler.py +++ b/homeassistant/components/mysensors/handler.py @@ -8,14 +8,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send 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 105/137] 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 2a6f036fa80..de0f2cc0a6f 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 def728244f5..ea34186457a 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 58885bf07e7..360fd281434 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 106/137] 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 ddd7548cd68..53531cf9ba9 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -13,10 +13,9 @@ from homeassistant.exceptions import ( 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 @@ async def handle_render_template(hass, connection, msg): 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 1f7abc42c4e..f596db63c5e 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -21,13 +21,7 @@ from tests.common import MockEntity, MockEntityPlatform, async_mock_service 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 @@ async def test_call_service(hass, websocket_client): 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 @@ async def test_call_service_target(hass, websocket_client): } +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 test_call_service_error(hass, websocket_client): ) 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 test_call_service_error(hass, websocket_client): ) 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 107/137] 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 8e6d907fc96..7277f540d76 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 108/137] 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 0b6fbbdc09a..0b7a59edb79 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 ea34186457a..33d2ee7aad6 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 939da2403f27d1b050d6b1bbd255a3e4f09fd267 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 5 Mar 2021 23:38:27 +0000 Subject: [PATCH 109/137] 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 e454f3ab09d..015a347a5e3 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 3b05a12e62139fc992f905f07943facfc8e51b86 Mon Sep 17 00:00:00 2001 From: Nathan Spencer Date: Sat, 6 Mar 2021 09:28:33 -0700 Subject: [PATCH 110/137] 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 | 95 +++++++++++++------ .../components/litterrobot/switch.py | 6 +- .../components/litterrobot/vacuum.py | 37 ++------ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/litterrobot/conftest.py | 72 ++++++++------ .../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, 277 insertions(+), 142 deletions(-) diff --git a/homeassistant/components/litterrobot/config_flow.py b/homeassistant/components/litterrobot/config_flow.py index d6c92d8dad6..36fc2064abb 100644 --- a/homeassistant/components/litterrobot/config_flow.py +++ b/homeassistant/components/litterrobot/config_flow.py @@ -35,9 +35,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): _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 0d0559140c7..943ef5bfe37 100644 --- a/homeassistant/components/litterrobot/hub.py +++ b/homeassistant/components/litterrobot/hub.py @@ -4,7 +4,7 @@ import logging 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 @@ class LitterRobotHub: 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 @@ class LitterRobotHub: 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 @@ class LitterRobotEntity(CoordinatorEntity): @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 1c6ac7274bf..8fa7ab8dcb5 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 2843660bcee..8900c6c54ca 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)) - - if entities: - async_add_entities(entities, True) +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" -class LitterRobotSensor(LitterRobotEntity, Entity): - """Litter-Robot sensors.""" +class LitterRobotPropertySensor(LitterRobotEntity, Entity): + """Litter-Robot property 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 @@ class LitterRobotSensor(LitterRobotEntity, Entity): @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 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 state(self): + """Return the state.""" + if self.robot.sleep_mode_active: + return super().state.isoformat() + return None + + @property + 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 b94b29a35e1..9164cc35e90 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 @@ class LitterRobotNightLightModeSwitch(LitterRobotEntity, ToggleEntity): 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 6ee92993869..4fe76d446f4 100644 --- a/homeassistant/components/litterrobot/vacuum.py +++ b/homeassistant/components/litterrobot/vacuum.py @@ -14,7 +14,6 @@ from homeassistant.components.vacuum import ( 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 @@ class LitterRobotCleaner(LitterRobotEntity, VacuumEntity): 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 @@ class LitterRobotCleaner(LitterRobotEntity, VacuumEntity): @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 33d2ee7aad6..688f9aefdce 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 360fd281434..3131f9c31e8 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 dae183b4cf6..aadf7d810aa 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 + 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 -@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 +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 fd88595d37e..5068ecf721b 100644 --- a/tests/components/litterrobot/test_config_flow.py +++ b/tests/components/litterrobot/test_config_flow.py @@ -4,11 +4,14 @@ from unittest.mock import patch 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 1d0ed075cc7..7cd36f33883 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 2421489e237..7f1570c553e 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 c7f85db7412..69154bef8f5 100644 --- a/tests/components/litterrobot/test_switch.py +++ b/tests/components/litterrobot/test_switch.py @@ -12,7 +12,7 @@ from homeassistant.components.switch import ( 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 @@ 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): +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 03e63b472b6..2db2ef21546 100644 --- a/tests/components/litterrobot/test_vacuum.py +++ b/tests/components/litterrobot/test_vacuum.py @@ -12,20 +12,21 @@ from homeassistant.components.vacuum import ( 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 111/137] 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 c042872f4cd..28e2683c259 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -616,7 +616,7 @@ class HomeKit: 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 ac3fb0251e2..d7ec3297fa4 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 688f9aefdce..112f69e817c 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 3131f9c31e8..f6f39403ac8 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 9ce3e96f06f..4d2fbfe951d 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 @@ async def test_homekit_too_many_accessories(hass, hk_driver, caplog, mock_zeroco 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 112/137] 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 e6ee45e7a57..1a9e9ef58df 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -646,9 +646,12 @@ class SonosEntity(MediaPlayerEntity): 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 113/137] 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 25b4d3106da..d7f1ffab400 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 @@ import voluptuous as vol 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 @@ from .const import ( CONF_TOPIC_OUT_PREFIX, CONF_VERSION, DOMAIN, + MYSENSORS_DISCOVERY, MYSENSORS_GATEWAYS, MYSENSORS_ON_UNLOAD, SUPPORTED_PLATFORMS_WITH_ENTRY_SUPPORT, @@ -43,6 +48,8 @@ _LOGGER = logging.getLogger(__name__) 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 @@ CONFIG_SCHEMA = vol.Schema( 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 b395a48f28b..d1f89e4fe04 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 d06bf0dee2f..4452dd0575b 100644 --- a/homeassistant/components/mysensors/helpers.py +++ b/homeassistant/components/mysensors/helpers.py @@ -9,7 +9,7 @@ from mysensors.sensor import ChildSensor 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 @@ SCHEMAS = Registry() @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 99e731762df..50fca55ab39 100644 --- a/homeassistant/components/mysensors/notify.py +++ b/homeassistant/components/mysensors/notify.py @@ -5,6 +5,9 @@ from homeassistant.components.notify import ATTR_TARGET, DOMAIN, BaseNotificatio 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 114/137] 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 b0a31203009..0e069f94e73 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 303b3cc3822..b3e3ec4d68d 100644 --- a/homeassistant/components/asuswrt/config_flow.py +++ b/homeassistant/components/asuswrt/config_flow.py @@ -128,7 +128,7 @@ class AsusWrtFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 744a05b9728..ab739f1c7ec 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 11545919b43..4af157387f9 100644 --- a/homeassistant/components/asuswrt/router.py +++ b/homeassistant/components/asuswrt/router.py @@ -205,7 +205,7 @@ class AsusWrtRouter: """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 7faec5d336c..a6e24b09462 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 994111370fd..0e663ae548b 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 115/137] 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 8371f2930c2..c7d439dedc4 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 112f69e817c..4ddf638867f 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 f6f39403ac8..0f1a59fff35 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 116/137] Fix mysensors device tracker (#47536) --- .../components/mysensors/__init__.py | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index d7f1ffab400..d0ab8ea712e 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -8,6 +8,7 @@ from mysensors import BaseAsyncGateway 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], - ) - - await on_unload( - hass, - entry.entry_id, - async_dispatcher_connect( + for platform in (DEVICE_TRACKER_DOMAIN, NOTIFY_DOMAIN): + load_discovery_platform = partial( + async_load_platform, hass, - MYSENSORS_DISCOVERY.format(entry.entry_id, NOTIFY_DOMAIN), - load_notify_platform, - ), - ) + platform, + 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, 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 117/137] 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 d0ab8ea712e..0f8123e3a31 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -38,11 +38,11 @@ from .const import ( 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 b6797cafb37..a7f3a053d3f 100644 --- a/homeassistant/components/mysensors/gateway.py +++ b/homeassistant/components/mysensors/gateway.py @@ -31,7 +31,12 @@ from .const import ( 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 @@ async def _gw_start( 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 4452dd0575b..0b8dc361158 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 @@ from .const import ( DOMAIN, FLAT_PLATFORM_TYPES, MYSENSORS_DISCOVERY, + MYSENSORS_ON_UNLOAD, TYPE_TO_PLATFORMS, DevId, GatewayId, @@ -31,6 +34,23 @@ _LOGGER = logging.getLogger(__name__) 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 118/137] 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 6a06b1dd391..619429c9a5b 100644 --- a/homeassistant/components/aemet/weather_update_coordinator.py +++ b/homeassistant/components/aemet/weather_update_coordinator.py @@ -393,7 +393,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): ), 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 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): 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 93db4ca26d8..e07a8f32608 100644 --- a/homeassistant/components/openweathermap/weather_update_coordinator.py +++ b/homeassistant/components/openweathermap/weather_update_coordinator.py @@ -139,7 +139,9 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): 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 05f2d8d0b50..b265b996709 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 eef6107d543..43acf4c1c87 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 119/137] 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 984e3f9c51a..a1409fd79a8 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 6283b2d8665..b13d7929042 100644 --- a/script/hassfest/dependencies.py +++ b/script/hassfest/dependencies.py @@ -147,6 +147,8 @@ IGNORE_VIOLATIONS = { # 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 120/137] 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 c061abc4d0d..7620323d940 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -105,7 +105,11 @@ class ZWaveBaseEntity(Entity): """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 aa9da282635..740d14763d4 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 e56db58f3cc..00574bd2d2f 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 00000000000..fe63eaee207 --- /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 121/137] 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 5b2e27b94cc..ec885f454e3 100644 --- a/homeassistant/components/bond/entity.py +++ b/homeassistant/components/bond/entity.py @@ -102,7 +102,7 @@ class BondEntity(Entity): 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 00000000000..e0a3f156ff5 --- /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 122/137] 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 8093c65d91a..694be0382f7 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 0586b956f39..7642e14bda8 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 4ddf638867f..5eb66a3a200 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 0f1a59fff35..628b62899cd 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 123/137] 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 e2cdf7b3cfd..591050f5fd9 100644 --- a/homeassistant/components/homekit_controller/fan.py +++ b/homeassistant/components/homekit_controller/fan.py @@ -80,6 +80,13 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): 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 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): 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 26adb25df21..62382eec5eb 100644 --- a/tests/components/homekit_controller/conftest.py +++ b/tests/components/homekit_controller/conftest.py @@ -11,6 +11,13 @@ import homeassistant.util.dt as dt_util 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 b8d42b21643..d66ce81d534 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 124/137] 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 18a7d8262e0..51dce0f8d56 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -523,7 +523,6 @@ class TemplateFan(TemplateEntity, FanEntity): 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 @@ class TemplateFan(TemplateEntity, FanEntity): 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 @@ class TemplateFan(TemplateEntity, FanEntity): 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 2b9059017c6..34dccd7d172 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 125/137] 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 a641d353450..2c397188640 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 .const import SIGNAL_ADD_ENTITIES 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 @@ class InsteonFanEntity(InsteonEntity, FanEntity): """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 @@ class InsteonFanEntity(InsteonEntity, FanEntity): **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 @@ class InsteonFanEntity(InsteonEntity, FanEntity): 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 126/137] 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 1ce6c364896..8ea19897420 100644 --- a/homeassistant/components/homekit/type_switches.py +++ b/homeassistant/components/homekit/type_switches.py @@ -27,7 +27,7 @@ from homeassistant.const import ( 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 @@ class Switch(HomeAccessory): 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 127/137] 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 0058374cfe7..27152997ef7 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 128/137] 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 325cf14b379..26e9e730283 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -162,6 +162,15 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): 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 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): 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 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): 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 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): @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 fe3e0708acc..a31aad19603 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -24,9 +24,17 @@ from homeassistant.components.climate.const import ( 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 129/137] 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 015a347a5e3..ae748b3ccc2 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 ed37c316304d0e2b741f7ac03d392fbcc42b85d5 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 11 Mar 2021 07:22:35 -0700 Subject: [PATCH 130/137] 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 7634f1cce86..8f394890ad4 100644 --- a/homeassistant/components/simplisafe/alarm_control_panel.py +++ b/homeassistant/components/simplisafe/alarm_control_panel.py @@ -163,6 +163,7 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity): 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 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity): 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 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity): 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 08ffb82d24f..a4d823efe38 100644 --- a/homeassistant/components/simplisafe/lock.py +++ b/homeassistant/components/simplisafe/lock.py @@ -55,6 +55,9 @@ class SimpliSafeLock(SimpliSafeEntity, LockEntity): 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 @@ class SimpliSafeLock(SimpliSafeEntity, LockEntity): 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 131/137] 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 7b7f983b1e4..e2a16c25329 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -280,15 +280,26 @@ class MqttCover(MqttEntity, CoverEntity): 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 44144642f40..d6899d5149a 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 132/137] 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 26e9e730283..4f62d5792fc 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -287,7 +287,12 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): @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 133/137] 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 2c397188640..f89aeb294fa 100644 --- a/homeassistant/components/insteon/fan.py +++ b/homeassistant/components/insteon/fan.py @@ -17,7 +17,7 @@ from .const import SIGNAL_ADD_ENTITIES 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 134/137] 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 49388bdfdb6..1319e4bbf49 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 5eb66a3a200..05dcf9f233e 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 628b62899cd..4dba304310e 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 135/137] 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 6f9c005ed64..a6b09a80e7f 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 136/137] 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 1fe7302038e..e41f83b8cc0 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 137/137] 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 ae748b3ccc2..d7bb7ea25b5 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)