From 93baf2439452839c3cef4769a0326499229ac028 Mon Sep 17 00:00:00 2001 From: j4n-e4t <130256240+j4n-e4t@users.noreply.github.com> Date: Mon, 5 Jun 2023 19:53:24 +0200 Subject: [PATCH 01/83] Add error handling to input_select integration (#93940) --- .../components/input_select/__init__.py | 7 ++--- tests/components/input_select/test_init.py | 26 ++++++++++--------- .../input_select/test_reproduce_state.py | 4 ++- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/input_select/__init__.py b/homeassistant/components/input_select/__init__.py index 186ab84fb81..2c5a1c87f29 100644 --- a/homeassistant/components/input_select/__init__.py +++ b/homeassistant/components/input_select/__init__.py @@ -302,12 +302,9 @@ class InputSelect(collection.CollectionEntity, SelectEntity, RestoreEntity): async def async_select_option(self, option: str) -> None: """Select new option.""" if option not in self.options: - _LOGGER.warning( - "Invalid option: %s (possible options: %s)", - option, - ", ".join(self.options), + raise HomeAssistantError( + f"Invalid option: {option} (possible options: {', '.join(self.options)})" ) - return self._attr_current_option = option self.async_write_ha_state() diff --git a/tests/components/input_select/test_init.py b/tests/components/input_select/test_init.py index 315392702eb..6908a1c532e 100644 --- a/tests/components/input_select/test_init.py +++ b/tests/components/input_select/test_init.py @@ -102,12 +102,13 @@ async def test_select_option(hass: HomeAssistant) -> None: state = hass.states.get(entity_id) assert state.state == "another option" - await hass.services.async_call( - DOMAIN, - SERVICE_SELECT_OPTION, - {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: "non existing option"}, - blocking=True, - ) + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + DOMAIN, + SERVICE_SELECT_OPTION, + {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: "non existing option"}, + blocking=True, + ) state = hass.states.get(entity_id) assert state.state == "another option" @@ -305,12 +306,13 @@ async def test_set_options_service(hass: HomeAssistant) -> None: state = hass.states.get(entity_id) assert state.state == "test1" - await hass.services.async_call( - DOMAIN, - SERVICE_SELECT_OPTION, - {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: "first option"}, - blocking=True, - ) + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + DOMAIN, + SERVICE_SELECT_OPTION, + {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: "first option"}, + blocking=True, + ) state = hass.states.get(entity_id) assert state.state == "test1" diff --git a/tests/components/input_select/test_reproduce_state.py b/tests/components/input_select/test_reproduce_state.py index d6e9274fa8d..a00b6b02ade 100644 --- a/tests/components/input_select/test_reproduce_state.py +++ b/tests/components/input_select/test_reproduce_state.py @@ -2,6 +2,7 @@ import pytest from homeassistant.core import HomeAssistant, State +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.state import async_reproduce_state from homeassistant.setup import async_setup_component @@ -60,7 +61,8 @@ async def test_reproducing_states( assert hass.states.get(ENTITY).state == VALID_OPTION3 # Test setting state to invalid state - await async_reproduce_state(hass, [State(ENTITY, INVALID_OPTION)]) + with pytest.raises(HomeAssistantError): + await async_reproduce_state(hass, [State(ENTITY, INVALID_OPTION)]) # The entity state should be unchanged assert hass.states.get(ENTITY).state == VALID_OPTION3 From e1c47fdb61d79497d0d18a006a814bf1bde3b9e3 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 7 Jun 2023 18:42:40 +0200 Subject: [PATCH 02/83] Fix OTBR reset (#94157) --- homeassistant/components/otbr/manifest.json | 2 +- homeassistant/components/otbr/util.py | 5 +++ .../components/otbr/websocket_api.py | 6 ++++ homeassistant/components/thread/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/otbr/test_websocket_api.py | 33 +++++++++++++++++-- 7 files changed, 46 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/otbr/manifest.json b/homeassistant/components/otbr/manifest.json index f04e15a549c..94659df8547 100644 --- a/homeassistant/components/otbr/manifest.json +++ b/homeassistant/components/otbr/manifest.json @@ -8,5 +8,5 @@ "documentation": "https://www.home-assistant.io/integrations/otbr", "integration_type": "service", "iot_class": "local_polling", - "requirements": ["python-otbr-api==2.1.0"] + "requirements": ["python-otbr-api==2.2.0"] } diff --git a/homeassistant/components/otbr/util.py b/homeassistant/components/otbr/util.py index 5caebba5eb5..2d6217ea585 100644 --- a/homeassistant/components/otbr/util.py +++ b/homeassistant/components/otbr/util.py @@ -95,6 +95,11 @@ class OTBRData: """Create an active operational dataset.""" return await self.api.create_active_dataset(dataset) + @_handle_otbr_error + async def delete_active_dataset(self) -> None: + """Delete the active operational dataset.""" + return await self.api.delete_active_dataset() + @_handle_otbr_error async def set_active_dataset_tlvs(self, dataset: bytes) -> None: """Set current active operational dataset in TLVS format.""" diff --git a/homeassistant/components/otbr/websocket_api.py b/homeassistant/components/otbr/websocket_api.py index 0dcce288348..06bbca3a4ab 100644 --- a/homeassistant/components/otbr/websocket_api.py +++ b/homeassistant/components/otbr/websocket_api.py @@ -81,6 +81,12 @@ async def websocket_create_network( connection.send_error(msg["id"], "set_enabled_failed", str(exc)) return + try: + await data.delete_active_dataset() + except HomeAssistantError as exc: + connection.send_error(msg["id"], "delete_active_dataset_failed", str(exc)) + return + try: await data.create_active_dataset( python_otbr_api.ActiveDataSet( diff --git a/homeassistant/components/thread/manifest.json b/homeassistant/components/thread/manifest.json index 9a6a64481cd..0ce54496539 100644 --- a/homeassistant/components/thread/manifest.json +++ b/homeassistant/components/thread/manifest.json @@ -7,6 +7,6 @@ "documentation": "https://www.home-assistant.io/integrations/thread", "integration_type": "service", "iot_class": "local_polling", - "requirements": ["python-otbr-api==2.1.0", "pyroute2==0.7.5"], + "requirements": ["python-otbr-api==2.2.0", "pyroute2==0.7.5"], "zeroconf": ["_meshcop._udp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index 8bb4580aa61..20df7fca52b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2106,7 +2106,7 @@ python-opensky==0.0.7 # homeassistant.components.otbr # homeassistant.components.thread -python-otbr-api==2.1.0 +python-otbr-api==2.2.0 # homeassistant.components.picnic python-picnic-api==1.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bc1cae28d09..36941aceeb4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1532,7 +1532,7 @@ python-nest==4.2.0 # homeassistant.components.otbr # homeassistant.components.thread -python-otbr-api==2.1.0 +python-otbr-api==2.2.0 # homeassistant.components.picnic python-picnic-api==1.1.0 diff --git a/tests/components/otbr/test_websocket_api.py b/tests/components/otbr/test_websocket_api.py index 1feebe9c02c..65bec9e8408 100644 --- a/tests/components/otbr/test_websocket_api.py +++ b/tests/components/otbr/test_websocket_api.py @@ -84,6 +84,8 @@ async def test_create_network( with patch( "python_otbr_api.OTBR.create_active_dataset" ) as create_dataset_mock, patch( + "python_otbr_api.OTBR.delete_active_dataset" + ) as delete_dataset_mock, patch( "python_otbr_api.OTBR.set_enabled" ) as set_enabled_mock, patch( "python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=DATASET_CH16 @@ -99,6 +101,7 @@ async def test_create_network( create_dataset_mock.assert_called_once_with( python_otbr_api.models.ActiveDataSet(channel=15, network_name="home-assistant") ) + delete_dataset_mock.assert_called_once_with() assert len(set_enabled_mock.mock_calls) == 2 assert set_enabled_mock.mock_calls[0][1][0] is False assert set_enabled_mock.mock_calls[1][1][0] is True @@ -151,7 +154,7 @@ async def test_create_network_fails_2( ), patch( "python_otbr_api.OTBR.create_active_dataset", side_effect=python_otbr_api.OTBRError, - ): + ), patch("python_otbr_api.OTBR.delete_active_dataset"): await websocket_client.send_json_auto_id({"type": "otbr/create_network"}) msg = await websocket_client.receive_json() @@ -171,6 +174,8 @@ async def test_create_network_fails_3( side_effect=[None, python_otbr_api.OTBRError], ), patch( "python_otbr_api.OTBR.create_active_dataset", + ), patch( + "python_otbr_api.OTBR.delete_active_dataset" ): await websocket_client.send_json_auto_id({"type": "otbr/create_network"}) msg = await websocket_client.receive_json() @@ -191,6 +196,8 @@ async def test_create_network_fails_4( ), patch( "python_otbr_api.OTBR.get_active_dataset_tlvs", side_effect=python_otbr_api.OTBRError, + ), patch( + "python_otbr_api.OTBR.delete_active_dataset" ): await websocket_client.send_json_auto_id({"type": "otbr/create_network"}) msg = await websocket_client.receive_json() @@ -208,7 +215,9 @@ async def test_create_network_fails_5( """Test create network.""" with patch("python_otbr_api.OTBR.set_enabled"), patch( "python_otbr_api.OTBR.create_active_dataset" - ), patch("python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=None): + ), patch("python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=None), patch( + "python_otbr_api.OTBR.delete_active_dataset" + ): await websocket_client.send_json_auto_id({"type": "otbr/create_network"}) msg = await websocket_client.receive_json() @@ -216,6 +225,26 @@ async def test_create_network_fails_5( assert msg["error"]["code"] == "get_active_dataset_tlvs_empty" +async def test_create_network_fails_6( + hass: HomeAssistant, + aioclient_mock: AiohttpClientMocker, + otbr_config_entry, + websocket_client, +) -> None: + """Test create network.""" + with patch("python_otbr_api.OTBR.set_enabled"), patch( + "python_otbr_api.OTBR.create_active_dataset" + ), patch("python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=None), patch( + "python_otbr_api.OTBR.delete_active_dataset", + side_effect=python_otbr_api.OTBRError, + ): + await websocket_client.send_json_auto_id({"type": "otbr/create_network"}) + msg = await websocket_client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == "delete_active_dataset_failed" + + async def test_set_network( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, From f24b514c9a999716048b091f8ef1878feaf94190 Mon Sep 17 00:00:00 2001 From: Justin Vanderhooft Date: Wed, 7 Jun 2023 17:17:01 +0100 Subject: [PATCH 03/83] Bump melnor-bluetooth to fix a timezone issue (#94159) --- homeassistant/components/melnor/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/melnor/manifest.json b/homeassistant/components/melnor/manifest.json index 185899a9656..45dce207f7e 100644 --- a/homeassistant/components/melnor/manifest.json +++ b/homeassistant/components/melnor/manifest.json @@ -12,5 +12,5 @@ "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/melnor", "iot_class": "local_polling", - "requirements": ["melnor-bluetooth==0.0.24"] + "requirements": ["melnor-bluetooth==0.0.25"] } diff --git a/requirements_all.txt b/requirements_all.txt index 20df7fca52b..9811498808b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1122,7 +1122,7 @@ mcstatus==6.0.0 meater-python==0.0.8 # homeassistant.components.melnor -melnor-bluetooth==0.0.24 +melnor-bluetooth==0.0.25 # homeassistant.components.message_bird messagebird==1.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 36941aceeb4..77b6710155f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -848,7 +848,7 @@ mcstatus==6.0.0 meater-python==0.0.8 # homeassistant.components.melnor -melnor-bluetooth==0.0.24 +melnor-bluetooth==0.0.25 # homeassistant.components.meteo_france meteofrance-api==1.2.0 From 74c0552a12c406fc29a1a413b7255ca2a0281480 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 7 Jun 2023 19:53:45 +0200 Subject: [PATCH 04/83] Fix Abode unit of measurement (#94168) Change unit of measurement to HA const --- homeassistant/components/abode/sensor.py | 3 ++- tests/components/abode/test_sensor.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/abode/sensor.py b/homeassistant/components/abode/sensor.py index 87a9f8e9a27..546d57ab3e7 100644 --- a/homeassistant/components/abode/sensor.py +++ b/homeassistant/components/abode/sensor.py @@ -12,6 +12,7 @@ from homeassistant.components.sensor import ( SensorEntityDescription, ) from homeassistant.config_entries import ConfigEntry +from homeassistant.const import LIGHT_LUX from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -71,7 +72,7 @@ class AbodeSensor(AbodeDevice, SensorEntity): elif description.key == CONST.HUMI_STATUS_KEY: self._attr_native_unit_of_measurement = device.humidity_unit elif description.key == CONST.LUX_STATUS_KEY: - self._attr_native_unit_of_measurement = device.lux_unit + self._attr_native_unit_of_measurement = LIGHT_LUX @property def native_value(self) -> float | None: diff --git a/tests/components/abode/test_sensor.py b/tests/components/abode/test_sensor.py index 5d074de214f..67892dfafb4 100644 --- a/tests/components/abode/test_sensor.py +++ b/tests/components/abode/test_sensor.py @@ -39,7 +39,7 @@ async def test_attributes(hass: HomeAssistant) -> None: state = hass.states.get("sensor.environment_sensor_lux") assert state.state == "1.0" - assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "lux" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "lx" state = hass.states.get("sensor.environment_sensor_temperature") # Abodepy device JSON reports 19.5, but Home Assistant shows 19.4 From c6a17d68320521866c7b374717c0bc4af8dd4fb7 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Thu, 8 Jun 2023 11:33:35 +0200 Subject: [PATCH 05/83] Bump pyoverkiz to 1.8.0 (#94176) --- homeassistant/components/overkiz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/overkiz/manifest.json b/homeassistant/components/overkiz/manifest.json index 2d81b7bab07..b03b60cd753 100644 --- a/homeassistant/components/overkiz/manifest.json +++ b/homeassistant/components/overkiz/manifest.json @@ -13,7 +13,7 @@ "integration_type": "hub", "iot_class": "cloud_polling", "loggers": ["boto3", "botocore", "pyhumps", "pyoverkiz", "s3transfer"], - "requirements": ["pyoverkiz==1.7.9"], + "requirements": ["pyoverkiz==1.8.0"], "zeroconf": [ { "type": "_kizbox._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index 9811498808b..0a93bc39854 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1872,7 +1872,7 @@ pyotgw==2.1.3 pyotp==2.8.0 # homeassistant.components.overkiz -pyoverkiz==1.7.9 +pyoverkiz==1.8.0 # homeassistant.components.openweathermap pyowm==3.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 77b6710155f..5449b3d3b8f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1382,7 +1382,7 @@ pyotgw==2.1.3 pyotp==2.8.0 # homeassistant.components.overkiz -pyoverkiz==1.7.9 +pyoverkiz==1.8.0 # homeassistant.components.openweathermap pyowm==3.2.0 From f39a6b96ff6e55a63cbf1666f4f39227f3a27462 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 8 Jun 2023 09:56:49 -0400 Subject: [PATCH 06/83] Rename Local Media to My Media (#94201) Co-authored-by: Martin Hjelmare Co-authored-by: Franck Nijhof --- homeassistant/components/media_source/local_source.py | 2 +- tests/components/media_source/test_init.py | 2 +- tests/components/roku/test_media_player.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/media_source/local_source.py b/homeassistant/components/media_source/local_source.py index c29794ae8d7..89437a6b2e0 100644 --- a/homeassistant/components/media_source/local_source.py +++ b/homeassistant/components/media_source/local_source.py @@ -38,7 +38,7 @@ def async_setup(hass: HomeAssistant) -> None: class LocalSource(MediaSource): """Provide local directories as media sources.""" - name: str = "Local Media" + name: str = "My media" def __init__(self, hass: HomeAssistant) -> None: """Initialize local source.""" diff --git a/tests/components/media_source/test_init.py b/tests/components/media_source/test_init.py index ec374e6a6e1..4e512608abf 100644 --- a/tests/components/media_source/test_init.py +++ b/tests/components/media_source/test_init.py @@ -95,7 +95,7 @@ async def test_async_browse_media(hass: HomeAssistant) -> None: media = await media_source.async_browse_media(hass, const.URI_SCHEME) assert isinstance(media, media_source.models.BrowseMediaSource) assert len(media.children) == 1 - assert media.children[0].title == "Local Media" + assert media.children[0].title == "My media" async def test_async_resolve_media(hass: HomeAssistant) -> None: diff --git a/tests/components/roku/test_media_player.py b/tests/components/roku/test_media_player.py index 1363cf7e286..5d4568ce7ac 100644 --- a/tests/components/roku/test_media_player.py +++ b/tests/components/roku/test_media_player.py @@ -864,7 +864,7 @@ async def test_media_browse_local_source( assert msg["result"]["children"][0]["title"] == "Apps" assert msg["result"]["children"][0]["media_content_type"] == MediaType.APPS - assert msg["result"]["children"][1]["title"] == "Local Media" + assert msg["result"]["children"][1]["title"] == "My media" assert msg["result"]["children"][1]["media_class"] == MediaClass.DIRECTORY assert msg["result"]["children"][1]["media_content_type"] is None assert ( @@ -892,7 +892,7 @@ async def test_media_browse_local_source( assert msg["success"] assert msg["result"] - assert msg["result"]["title"] == "Local Media" + assert msg["result"]["title"] == "My media" assert msg["result"]["media_class"] == MediaClass.DIRECTORY assert msg["result"]["media_content_type"] is None assert len(msg["result"]["children"]) == 2 From 13029cf26fb248fcf7225cddf117ea8602fc6bc8 Mon Sep 17 00:00:00 2001 From: James Connor Date: Thu, 8 Jun 2023 08:43:30 +0100 Subject: [PATCH 07/83] Fix ambiclimate for Python 3.11 (#94203) Fix ambiclimate python 3.11 break --- homeassistant/components/ambiclimate/climate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/ambiclimate/climate.py b/homeassistant/components/ambiclimate/climate.py index 2bb2b441430..516ed319d01 100644 --- a/homeassistant/components/ambiclimate/climate.py +++ b/homeassistant/components/ambiclimate/climate.py @@ -98,7 +98,7 @@ async def async_setup_entry( tasks = [] for heater in data_connection.get_devices(): - tasks.append(heater.update_device_info()) + tasks.append(asyncio.create_task(heater.update_device_info())) await asyncio.wait(tasks) devs = [] From ac963a2b6e136db6b3c38156cc07aba8769f0429 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Thu, 8 Jun 2023 00:41:45 -0700 Subject: [PATCH 08/83] Require pydantic 1.10.8 or higher (#94208) * Requied pydantic 1.10.9 or higher * Simplify constraint to 2.0 * Drop constraint by one patch release to 1.10.8 or higher * Add package constraints to gen requirements script --- homeassistant/package_constraints.txt | 5 ++--- script/gen_requirements_all.py | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 22d47a0e430..60389bd5895 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -128,9 +128,8 @@ authlib<1.0 # Version 2.0 added typing, prevent accidental fallbacks backoff>=2.0 -# Breaking change in version -# https://github.com/samuelcolvin/pydantic/issues/4092 -pydantic!=1.9.1 +# Require to avoid issues with decorators (#93904). v2 has breaking changes. +pydantic>=1.10.8,<2.0 # Breaks asyncio # https://github.com/pubnub/python/issues/130 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index b51ddb46307..1b5969a6e86 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -132,9 +132,8 @@ authlib<1.0 # Version 2.0 added typing, prevent accidental fallbacks backoff>=2.0 -# Breaking change in version -# https://github.com/samuelcolvin/pydantic/issues/4092 -pydantic!=1.9.1 +# Require to avoid issues with decorators (#93904). v2 has breaking changes. +pydantic>=1.10.8,<2.0 # Breaks asyncio # https://github.com/pubnub/python/issues/130 From b508875f17fbc7624c240a82efdb93361702fbe1 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Thu, 8 Jun 2023 09:39:06 +0200 Subject: [PATCH 09/83] Set httpx log level to warning (#94217) Set log level of httpx to warning --- homeassistant/bootstrap.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 7e5aa853f12..6a667884962 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -391,6 +391,7 @@ def async_enable_logging( logging.getLogger("requests").setLevel(logging.WARNING) logging.getLogger("urllib3").setLevel(logging.WARNING) logging.getLogger("aiohttp.access").setLevel(logging.WARNING) + logging.getLogger("httpx").setLevel(logging.WARNING) sys.excepthook = lambda *args: logging.getLogger(None).exception( "Uncaught exception", exc_info=args # type: ignore[arg-type] From 33bf8c600b58ac3ea21e7b1f330290ec74393118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Thu, 8 Jun 2023 11:34:56 +0200 Subject: [PATCH 10/83] Update aioairzone-cloud to v0.1.8 (#94223) --- homeassistant/components/airzone_cloud/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/airzone_cloud/manifest.json b/homeassistant/components/airzone_cloud/manifest.json index b2899a7c80c..e64a5d9a7e2 100644 --- a/homeassistant/components/airzone_cloud/manifest.json +++ b/homeassistant/components/airzone_cloud/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/airzone_cloud", "iot_class": "cloud_polling", "loggers": ["aioairzone_cloud"], - "requirements": ["aioairzone-cloud==0.1.7"] + "requirements": ["aioairzone-cloud==0.1.8"] } diff --git a/requirements_all.txt b/requirements_all.txt index 0a93bc39854..4e11e624a0d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -116,7 +116,7 @@ aio_georss_gdacs==0.8 aioairq==0.2.4 # homeassistant.components.airzone_cloud -aioairzone-cloud==0.1.7 +aioairzone-cloud==0.1.8 # homeassistant.components.airzone aioairzone==0.6.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5449b3d3b8f..c6deeee2761 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -106,7 +106,7 @@ aio_georss_gdacs==0.8 aioairq==0.2.4 # homeassistant.components.airzone_cloud -aioairzone-cloud==0.1.7 +aioairzone-cloud==0.1.8 # homeassistant.components.airzone aioairzone==0.6.3 From 4509e13cebdcd451bed9c84fbf330ae82c244a52 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 1 Jun 2023 17:01:51 +0200 Subject: [PATCH 11/83] Bump python-opensky (#93916) --- homeassistant/components/opensky/manifest.json | 2 +- homeassistant/components/opensky/sensor.py | 2 +- requirements_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/opensky/manifest.json b/homeassistant/components/opensky/manifest.json index 854f2ec840b..460453968b6 100644 --- a/homeassistant/components/opensky/manifest.json +++ b/homeassistant/components/opensky/manifest.json @@ -4,5 +4,5 @@ "codeowners": ["@joostlek"], "documentation": "https://www.home-assistant.io/integrations/opensky", "iot_class": "cloud_polling", - "requirements": ["python-opensky==0.0.7"] + "requirements": ["python-opensky==0.0.8"] } diff --git a/homeassistant/components/opensky/sensor.py b/homeassistant/components/opensky/sensor.py index f3704f8d547..cdedd0c9620 100644 --- a/homeassistant/components/opensky/sensor.py +++ b/homeassistant/components/opensky/sensor.py @@ -78,7 +78,7 @@ def setup_platform( latitude = config.get(CONF_LATITUDE, hass.config.latitude) longitude = config.get(CONF_LONGITUDE, hass.config.longitude) radius = config.get(CONF_RADIUS, 0) - bounding_box = OpenSky.get_bounding_box(latitude, longitude, radius) + bounding_box = OpenSky.get_bounding_box(latitude, longitude, radius * 1000) session = async_get_clientsession(hass) opensky = OpenSky(session=session) add_entities( diff --git a/requirements_all.txt b/requirements_all.txt index 4e11e624a0d..1e339f383f0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2102,7 +2102,7 @@ python-mystrom==2.2.0 python-nest==4.2.0 # homeassistant.components.opensky -python-opensky==0.0.7 +python-opensky==0.0.8 # homeassistant.components.otbr # homeassistant.components.thread From 3b27a3aabff1e8719044eb0c627fd9af814b8ee3 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 8 Jun 2023 11:13:24 +0200 Subject: [PATCH 12/83] Bump python-opensky to 0.0.9 (#94224) --- homeassistant/components/opensky/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/opensky/manifest.json b/homeassistant/components/opensky/manifest.json index 460453968b6..cda86006bbd 100644 --- a/homeassistant/components/opensky/manifest.json +++ b/homeassistant/components/opensky/manifest.json @@ -4,5 +4,5 @@ "codeowners": ["@joostlek"], "documentation": "https://www.home-assistant.io/integrations/opensky", "iot_class": "cloud_polling", - "requirements": ["python-opensky==0.0.8"] + "requirements": ["python-opensky==0.0.9"] } diff --git a/requirements_all.txt b/requirements_all.txt index 1e339f383f0..0fbe396033c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2102,7 +2102,7 @@ python-mystrom==2.2.0 python-nest==4.2.0 # homeassistant.components.opensky -python-opensky==0.0.8 +python-opensky==0.0.9 # homeassistant.components.otbr # homeassistant.components.thread From 413e1c97d701ab0ece401d46c012aba83ec272ae Mon Sep 17 00:00:00 2001 From: Kostas Chatzikokolakis Date: Thu, 8 Jun 2023 16:59:16 +0300 Subject: [PATCH 13/83] Bump pulsectl to 23.5.2 (#94227) --- homeassistant/components/pulseaudio_loopback/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/pulseaudio_loopback/manifest.json b/homeassistant/components/pulseaudio_loopback/manifest.json index f04538c01bb..a67dc614c50 100644 --- a/homeassistant/components/pulseaudio_loopback/manifest.json +++ b/homeassistant/components/pulseaudio_loopback/manifest.json @@ -4,5 +4,5 @@ "codeowners": [], "documentation": "https://www.home-assistant.io/integrations/pulseaudio_loopback", "iot_class": "local_polling", - "requirements": ["pulsectl==20.2.4"] + "requirements": ["pulsectl==23.5.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 0fbe396033c..f1b8642fce3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1418,7 +1418,7 @@ psutil-home-assistant==0.0.1 psutil==5.9.5 # homeassistant.components.pulseaudio_loopback -pulsectl==20.2.4 +pulsectl==23.5.2 # homeassistant.components.androidtv pure-python-adb[async]==0.3.0.dev0 From 0cf3825183ab341aff146b62d2869aec97dcc9c5 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Thu, 8 Jun 2023 11:11:12 +0200 Subject: [PATCH 14/83] Fix imap crash on email without subject (#94230) --- homeassistant/components/imap/coordinator.py | 2 +- tests/components/imap/const.py | 20 ++++++++++ tests/components/imap/test_init.py | 41 +++++++++++++++++++- 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/imap/coordinator.py b/homeassistant/components/imap/coordinator.py index d41aaf8c497..bf7f173e647 100644 --- a/homeassistant/components/imap/coordinator.py +++ b/homeassistant/components/imap/coordinator.py @@ -120,7 +120,7 @@ class ImapMessage: @property def subject(self) -> str: """Decode the message subject.""" - decoded_header = decode_header(self.email_message["Subject"]) + decoded_header = decode_header(self.email_message["Subject"] or "") subject_header = make_header(decoded_header) return str(subject_header) diff --git a/tests/components/imap/const.py b/tests/components/imap/const.py index 15b56547894..5dcce782a41 100644 --- a/tests/components/imap/const.py +++ b/tests/components/imap/const.py @@ -24,7 +24,12 @@ TEST_MESSAGE_HEADERS2 = ( b"Subject: Test subject\r\n" ) +TEST_MESSAGE_HEADERS3 = b"" + TEST_MESSAGE = TEST_MESSAGE_HEADERS1 + DATE_HEADER1 + TEST_MESSAGE_HEADERS2 +TEST_MESSAGE_NO_SUBJECT_TO_FROM = ( + TEST_MESSAGE_HEADERS1 + DATE_HEADER1 + TEST_MESSAGE_HEADERS3 +) TEST_MESSAGE_ALT = TEST_MESSAGE_HEADERS1 + DATE_HEADER2 + TEST_MESSAGE_HEADERS2 TEST_INVALID_DATE1 = ( TEST_MESSAGE_HEADERS1 + DATE_HEADER_INVALID1 + TEST_MESSAGE_HEADERS2 @@ -204,4 +209,19 @@ TEST_FETCH_RESPONSE_MULTIPART = ( ], ) + +TEST_FETCH_RESPONSE_NO_SUBJECT_TO_FROM = ( + "OK", + [ + b"1 FETCH (BODY[] {" + + str(len(TEST_MESSAGE_NO_SUBJECT_TO_FROM + TEST_CONTENT_TEXT_PLAIN)).encode( + "utf-8" + ) + + b"}", + bytearray(TEST_MESSAGE_NO_SUBJECT_TO_FROM + TEST_CONTENT_TEXT_PLAIN), + b")", + b"Fetch completed (0.0001 + 0.000 secs).", + ], +) + RESPONSE_BAD = ("BAD", []) diff --git a/tests/components/imap/test_init.py b/tests/components/imap/test_init.py index 712f159b4cb..2b7514cd3ea 100644 --- a/tests/components/imap/test_init.py +++ b/tests/components/imap/test_init.py @@ -1,6 +1,6 @@ """Test the imap entry initialization.""" import asyncio -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import Any from unittest.mock import AsyncMock, MagicMock, patch @@ -22,6 +22,7 @@ from .const import ( TEST_FETCH_RESPONSE_INVALID_DATE2, TEST_FETCH_RESPONSE_INVALID_DATE3, TEST_FETCH_RESPONSE_MULTIPART, + TEST_FETCH_RESPONSE_NO_SUBJECT_TO_FROM, TEST_FETCH_RESPONSE_TEXT_BARE, TEST_FETCH_RESPONSE_TEXT_OTHER, TEST_FETCH_RESPONSE_TEXT_PLAIN, @@ -153,6 +154,44 @@ async def test_receiving_message_successfully( ) +@pytest.mark.parametrize("imap_search", [TEST_SEARCH_RESPONSE]) +@pytest.mark.parametrize("imap_fetch", [TEST_FETCH_RESPONSE_NO_SUBJECT_TO_FROM]) +@pytest.mark.parametrize("imap_has_capability", [True, False], ids=["push", "poll"]) +async def test_receiving_message_no_subject_to_from( + hass: HomeAssistant, mock_imap_protocol: MagicMock +) -> None: + """Test receiving a message successfully without subject, to and from in body.""" + event_called = async_capture_events(hass, "imap_content") + + config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG) + config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + # Make sure we have had one update (when polling) + async_fire_time_changed(hass, utcnow() + timedelta(seconds=5)) + await hass.async_block_till_done() + state = hass.states.get("sensor.imap_email_email_com") + # we should have received one message + assert state is not None + assert state.state == "1" + + # we should have received one event + assert len(event_called) == 1 + data: dict[str, Any] = event_called[0].data + assert data["server"] == "imap.server.com" + assert data["username"] == "email@email.com" + assert data["search"] == "UnSeen UnDeleted" + assert data["folder"] == "INBOX" + assert data["sender"] == "" + assert data["subject"] == "" + assert data["date"] == datetime( + 2023, 3, 24, 13, 52, tzinfo=timezone(timedelta(seconds=3600)) + ) + assert data["text"] == "Test body\r\n\r\n" + assert data["headers"]["Return-Path"] == ("",) + assert data["headers"]["Delivered-To"] == ("notify@example.com",) + + @pytest.mark.parametrize("imap_has_capability", [True, False], ids=["push", "poll"]) @pytest.mark.parametrize( ("imap_login_state", "success"), [(AUTH, True), (NONAUTH, False)] From 2b1c45c28c75a221c6c5d24d33d6ade1c9bef47e Mon Sep 17 00:00:00 2001 From: jan iversen Date: Thu, 8 Jun 2023 11:29:54 +0200 Subject: [PATCH 15/83] Solve wrong return code from modbus. (#94234) --- homeassistant/components/flexit/climate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/flexit/climate.py b/homeassistant/components/flexit/climate.py index ac8f4b4da8c..838d2c934f9 100644 --- a/homeassistant/components/flexit/climate.py +++ b/homeassistant/components/flexit/climate.py @@ -192,7 +192,7 @@ class Flexit(ClimateEntity): result = float( await self._async_read_int16_from_register(register_type, register) ) - if result == -1: + if not result: return -1 return result / 10.0 @@ -200,6 +200,6 @@ class Flexit(ClimateEntity): result = await self._hub.async_pymodbus_call( self._slave, register, value, CALL_TYPE_WRITE_REGISTER ) - if result == -1: + if not result: return False return True From 8705a26a1a6611c9a389b8fe229554cd1605e589 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 8 Jun 2023 16:08:18 +0200 Subject: [PATCH 16/83] Catch exception when user has no lastfm friends (#94235) --- homeassistant/components/lastfm/config_flow.py | 6 +++--- tests/components/lastfm/__init__.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/lastfm/config_flow.py b/homeassistant/components/lastfm/config_flow.py index f7d7a9fd314..9b7628cb39a 100644 --- a/homeassistant/components/lastfm/config_flow.py +++ b/homeassistant/components/lastfm/config_flow.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Any -from pylast import LastFMNetwork, User, WSError +from pylast import LastFMNetwork, PyLastError, User, WSError import voluptuous as vol from homeassistant.config_entries import ( @@ -132,7 +132,7 @@ class LastFmConfigFlowHandler(ConfigFlow, domain=DOMAIN): SelectOptionDict(value=friend.name, label=friend.get_name(True)) for friend in main_user.get_friends() ] - except WSError: + except PyLastError: friends = [] return self.async_show_form( step_id="friends", @@ -201,7 +201,7 @@ class LastFmOptionsFlowHandler(OptionsFlowWithConfigEntry): SelectOptionDict(value=friend.name, label=friend.get_name(True)) for friend in main_user.get_friends() ] - except WSError: + except PyLastError: friends = [] else: friends = [] diff --git a/tests/components/lastfm/__init__.py b/tests/components/lastfm/__init__.py index 568983f400d..7ee8665e28a 100644 --- a/tests/components/lastfm/__init__.py +++ b/tests/components/lastfm/__init__.py @@ -1,7 +1,7 @@ """The tests for lastfm.""" from unittest.mock import patch -from pylast import Track, WSError +from pylast import PyLastError, Track from homeassistant.components.lastfm.const import CONF_MAIN_USER, CONF_USERS from homeassistant.const import CONF_API_KEY @@ -65,7 +65,7 @@ class MockUser: def get_friends(self): """Get mock friends.""" if self._has_friends is False: - raise WSError("network", "status", "Page not found") + raise PyLastError("network", "status", "Page not found") return [MockUser(None, None, True, USERNAME_2)] From a3fda43c6426f97373c015b0fcc1c0401b18253f Mon Sep 17 00:00:00 2001 From: Jc2k Date: Thu, 8 Jun 2023 14:10:12 +0100 Subject: [PATCH 17/83] Bump aiohomekit to 2.6.5 (fixes python 3.11 regression) (#94245) --- homeassistant/components/homekit_controller/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index 89261df8751..19167e762e9 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -14,6 +14,6 @@ "documentation": "https://www.home-assistant.io/integrations/homekit_controller", "iot_class": "local_push", "loggers": ["aiohomekit", "commentjson"], - "requirements": ["aiohomekit==2.6.4"], + "requirements": ["aiohomekit==2.6.5"], "zeroconf": ["_hap._tcp.local.", "_hap._udp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index f1b8642fce3..8fce6b13a04 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -177,7 +177,7 @@ aioguardian==2022.07.0 aioharmony==0.2.10 # homeassistant.components.homekit_controller -aiohomekit==2.6.4 +aiohomekit==2.6.5 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c6deeee2761..9a73ebc5e05 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -164,7 +164,7 @@ aioguardian==2022.07.0 aioharmony==0.2.10 # homeassistant.components.homekit_controller -aiohomekit==2.6.4 +aiohomekit==2.6.5 # homeassistant.components.emulated_hue # homeassistant.components.http From d861292900b0128c51f8fca04912e7c79420b344 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 8 Jun 2023 15:55:09 +0200 Subject: [PATCH 18/83] Retrieve friends in an async manner in Lastfm (#94255) --- homeassistant/components/lastfm/config_flow.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/lastfm/config_flow.py b/homeassistant/components/lastfm/config_flow.py index 9b7628cb39a..54406a6e03b 100644 --- a/homeassistant/components/lastfm/config_flow.py +++ b/homeassistant/components/lastfm/config_flow.py @@ -128,9 +128,12 @@ class LastFmConfigFlowHandler(ConfigFlow, domain=DOMAIN): main_user, _ = get_lastfm_user( self.data[CONF_API_KEY], self.data[CONF_MAIN_USER] ) + friends_response = await self.hass.async_add_executor_job( + main_user.get_friends + ) friends = [ SelectOptionDict(value=friend.name, label=friend.get_name(True)) - for friend in main_user.get_friends() + for friend in friends_response ] except PyLastError: friends = [] @@ -197,9 +200,12 @@ class LastFmOptionsFlowHandler(OptionsFlowWithConfigEntry): self.options[CONF_API_KEY], self.options[CONF_MAIN_USER], ) + friends_response = await self.hass.async_add_executor_job( + main_user.get_friends + ) friends = [ SelectOptionDict(value=friend.name, label=friend.get_name(True)) - for friend in main_user.get_friends() + for friend in friends_response ] except PyLastError: friends = [] From 5da0ef36eacbdc2e870ff8aa8b5bbcc0d8f1e187 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 8 Jun 2023 15:59:56 +0200 Subject: [PATCH 19/83] Update frontend to 20230608.0 (#94256) --- 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 af8898f28e2..00753021b4c 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/frontend", "integration_type": "system", "quality_scale": "internal", - "requirements": ["home-assistant-frontend==20230607.0"] + "requirements": ["home-assistant-frontend==20230608.0"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 60389bd5895..a7688415905 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -25,7 +25,7 @@ ha-av==10.1.0 hass-nabucasa==0.67.1 hassil==1.0.6 home-assistant-bluetooth==1.10.0 -home-assistant-frontend==20230607.0 +home-assistant-frontend==20230608.0 home-assistant-intents==2023.6.5 httpx==0.24.1 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index 8fce6b13a04..14e41852492 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -924,7 +924,7 @@ hole==0.8.0 holidays==0.21.13 # homeassistant.components.frontend -home-assistant-frontend==20230607.0 +home-assistant-frontend==20230608.0 # homeassistant.components.conversation home-assistant-intents==2023.6.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9a73ebc5e05..e79f30e5c7c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -716,7 +716,7 @@ hole==0.8.0 holidays==0.21.13 # homeassistant.components.frontend -home-assistant-frontend==20230607.0 +home-assistant-frontend==20230608.0 # homeassistant.components.conversation home-assistant-intents==2023.6.5 From 2801ba6cad30216eda5e1250c3554ab2a6372113 Mon Sep 17 00:00:00 2001 From: Christopher Bailey Date: Thu, 8 Jun 2023 11:08:52 -0400 Subject: [PATCH 20/83] Bump unifiprotect to 4.10.2 (#94263) --- homeassistant/components/unifiprotect/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/unifiprotect/manifest.json b/homeassistant/components/unifiprotect/manifest.json index 78e2ee3012c..a414c03a0d4 100644 --- a/homeassistant/components/unifiprotect/manifest.json +++ b/homeassistant/components/unifiprotect/manifest.json @@ -41,7 +41,7 @@ "iot_class": "local_push", "loggers": ["pyunifiprotect", "unifi_discovery"], "quality_scale": "platinum", - "requirements": ["pyunifiprotect==4.10.1", "unifi-discovery==1.1.7"], + "requirements": ["pyunifiprotect==4.10.2", "unifi-discovery==1.1.7"], "ssdp": [ { "manufacturer": "Ubiquiti Networks", diff --git a/requirements_all.txt b/requirements_all.txt index 14e41852492..eca5516f7ec 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2168,7 +2168,7 @@ pytrafikverket==0.3.3 pyudev==0.23.2 # homeassistant.components.unifiprotect -pyunifiprotect==4.10.1 +pyunifiprotect==4.10.2 # homeassistant.components.uptimerobot pyuptimerobot==22.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e79f30e5c7c..7f48debc62b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1579,7 +1579,7 @@ pytrafikverket==0.3.3 pyudev==0.23.2 # homeassistant.components.unifiprotect -pyunifiprotect==4.10.1 +pyunifiprotect==4.10.2 # homeassistant.components.uptimerobot pyuptimerobot==22.2.0 From 40bb796f03fd4d223de3043128427464ce51aa57 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 8 Jun 2023 12:00:34 -0400 Subject: [PATCH 21/83] Fix default value when logger used (#94269) --- homeassistant/components/logger/__init__.py | 5 +---- homeassistant/components/logger/helpers.py | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/logger/__init__.py b/homeassistant/components/logger/__init__.py index fe29447aeba..b1086d7f780 100644 --- a/homeassistant/components/logger/__init__.py +++ b/homeassistant/components/logger/__init__.py @@ -13,7 +13,6 @@ from homeassistant.helpers.typing import ConfigType from . import websocket_api from .const import ( ATTR_LEVEL, - DEFAULT_LOGSEVERITY, DOMAIN, LOGGER_DEFAULT, LOGGER_FILTERS, @@ -39,9 +38,7 @@ CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( { - vol.Optional( - LOGGER_DEFAULT, default=DEFAULT_LOGSEVERITY - ): _VALID_LOG_LEVEL, + vol.Optional(LOGGER_DEFAULT): _VALID_LOG_LEVEL, vol.Optional(LOGGER_LOGS): vol.Schema({cv.string: _VALID_LOG_LEVEL}), vol.Optional(LOGGER_FILTERS): vol.Schema({cv.string: [cv.is_regex]}), } diff --git a/homeassistant/components/logger/helpers.py b/homeassistant/components/logger/helpers.py index 0f1751c1b2e..dcd4348a561 100644 --- a/homeassistant/components/logger/helpers.py +++ b/homeassistant/components/logger/helpers.py @@ -119,7 +119,7 @@ class LoggerSettings: self._yaml_config = yaml_config self._default_level = logging.INFO - if DOMAIN in yaml_config: + if DOMAIN in yaml_config and LOGGER_DEFAULT in yaml_config[DOMAIN]: self._default_level = yaml_config[DOMAIN][LOGGER_DEFAULT] self._store: Store[dict[str, dict[str, dict[str, Any]]]] = Store( hass, STORAGE_VERSION, STORAGE_KEY From b39b0a960eef176164212ed18df09bfc3dd71670 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 8 Jun 2023 18:22:34 +0200 Subject: [PATCH 22/83] Fix repair issue about no yaml for config entries (#94271) --- homeassistant/setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/setup.py b/homeassistant/setup.py index d4b9be05ef4..fc5aa8291b7 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -245,7 +245,10 @@ async def _async_setup_component( severity=IssueSeverity.ERROR, issue_domain=domain, translation_key="integration_key_no_support", - translation_placeholders={"domain": domain}, + translation_placeholders={ + "domain": domain, + "add_integration": f"/config/integrations/dashboard/add?domain={domain}", + }, ) start = timer() From 602fcd6b1b1b886f88d3dce5c628740afb30ef81 Mon Sep 17 00:00:00 2001 From: Tom Harris Date: Thu, 1 Jun 2023 08:50:35 +0200 Subject: [PATCH 23/83] Restructure Insteon start-up (#92818) * Restructure startup * Code review * Further typing * Fix circular import --- .../components/insteon/binary_sensor.py | 9 +++- homeassistant/components/insteon/climate.py | 9 +++- homeassistant/components/insteon/cover.py | 9 +++- homeassistant/components/insteon/fan.py | 9 +++- homeassistant/components/insteon/ipdb.py | 15 +++--- homeassistant/components/insteon/light.py | 9 +++- homeassistant/components/insteon/lock.py | 9 +++- homeassistant/components/insteon/switch.py | 9 +++- homeassistant/components/insteon/utils.py | 51 ++++++++++++++----- 9 files changed, 97 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/insteon/binary_sensor.py b/homeassistant/components/insteon/binary_sensor.py index 9d1ec352bed..f895b9c7f6a 100644 --- a/homeassistant/components/insteon/binary_sensor.py +++ b/homeassistant/components/insteon/binary_sensor.py @@ -25,7 +25,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import SIGNAL_ADD_ENTITIES from .insteon_entity import InsteonEntity -from .utils import async_add_insteon_entities +from .utils import async_add_insteon_devices, async_add_insteon_entities SENSOR_TYPES = { OPEN_CLOSE_SENSOR: BinarySensorDeviceClass.OPENING, @@ -62,7 +62,12 @@ async def async_setup_entry( signal = f"{SIGNAL_ADD_ENTITIES}_{Platform.BINARY_SENSOR}" async_dispatcher_connect(hass, signal, async_add_insteon_binary_sensor_entities) - async_add_insteon_binary_sensor_entities() + async_add_insteon_devices( + hass, + Platform.BINARY_SENSOR, + InsteonBinarySensorEntity, + async_add_entities, + ) class InsteonBinarySensorEntity(InsteonEntity, BinarySensorEntity): diff --git a/homeassistant/components/insteon/climate.py b/homeassistant/components/insteon/climate.py index cf5f4ac2c0c..48ff898d6aa 100644 --- a/homeassistant/components/insteon/climate.py +++ b/homeassistant/components/insteon/climate.py @@ -23,7 +23,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import SIGNAL_ADD_ENTITIES from .insteon_entity import InsteonEntity -from .utils import async_add_insteon_entities +from .utils import async_add_insteon_devices, async_add_insteon_entities FAN_ONLY = "fan_only" @@ -71,7 +71,12 @@ async def async_setup_entry( signal = f"{SIGNAL_ADD_ENTITIES}_{Platform.CLIMATE}" async_dispatcher_connect(hass, signal, async_add_insteon_climate_entities) - async_add_insteon_climate_entities() + async_add_insteon_devices( + hass, + Platform.CLIMATE, + InsteonClimateEntity, + async_add_entities, + ) class InsteonClimateEntity(InsteonEntity, ClimateEntity): diff --git a/homeassistant/components/insteon/cover.py b/homeassistant/components/insteon/cover.py index 69a66d304ce..0756e603579 100644 --- a/homeassistant/components/insteon/cover.py +++ b/homeassistant/components/insteon/cover.py @@ -15,7 +15,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import SIGNAL_ADD_ENTITIES from .insteon_entity import InsteonEntity -from .utils import async_add_insteon_entities +from .utils import async_add_insteon_devices, async_add_insteon_entities async def async_setup_entry( @@ -34,7 +34,12 @@ async def async_setup_entry( signal = f"{SIGNAL_ADD_ENTITIES}_{Platform.COVER}" async_dispatcher_connect(hass, signal, async_add_insteon_cover_entities) - async_add_insteon_cover_entities() + async_add_insteon_devices( + hass, + Platform.COVER, + InsteonCoverEntity, + async_add_entities, + ) class InsteonCoverEntity(InsteonEntity, CoverEntity): diff --git a/homeassistant/components/insteon/fan.py b/homeassistant/components/insteon/fan.py index b0d664a821b..92f56098a91 100644 --- a/homeassistant/components/insteon/fan.py +++ b/homeassistant/components/insteon/fan.py @@ -17,7 +17,7 @@ from homeassistant.util.percentage import ( from .const import SIGNAL_ADD_ENTITIES from .insteon_entity import InsteonEntity -from .utils import async_add_insteon_entities +from .utils import async_add_insteon_devices, async_add_insteon_entities SPEED_RANGE = (1, 255) # off is not included @@ -38,7 +38,12 @@ async def async_setup_entry( signal = f"{SIGNAL_ADD_ENTITIES}_{Platform.FAN}" async_dispatcher_connect(hass, signal, async_add_insteon_fan_entities) - async_add_insteon_fan_entities() + async_add_insteon_devices( + hass, + Platform.FAN, + InsteonFanEntity, + async_add_entities, + ) class InsteonFanEntity(InsteonEntity, FanEntity): diff --git a/homeassistant/components/insteon/ipdb.py b/homeassistant/components/insteon/ipdb.py index ee799e103f9..de3ba7d55f2 100644 --- a/homeassistant/components/insteon/ipdb.py +++ b/homeassistant/components/insteon/ipdb.py @@ -1,4 +1,7 @@ """Utility methods for the Insteon platform.""" +from collections.abc import Iterable + +from pyinsteon.device_types.device_base import Device from pyinsteon.device_types.ipdb import ( AccessControl_Morningstar, ClimateControl_Thermostat, @@ -44,7 +47,7 @@ from pyinsteon.device_types.ipdb import ( from homeassistant.const import Platform -DEVICE_PLATFORM = { +DEVICE_PLATFORM: dict[Device, dict[Platform, Iterable[int]]] = { AccessControl_Morningstar: {Platform.LOCK: [1]}, DimmableLightingControl: {Platform.LIGHT: [1]}, DimmableLightingControl_Dial: {Platform.LIGHT: [1]}, @@ -101,11 +104,11 @@ DEVICE_PLATFORM = { } -def get_device_platforms(device): +def get_device_platforms(device) -> dict[Platform, Iterable[int]]: """Return the HA platforms for a device type.""" - return DEVICE_PLATFORM.get(type(device), {}).keys() + return DEVICE_PLATFORM.get(type(device), {}) -def get_platform_groups(device, domain) -> dict: - """Return the platforms that a device belongs in.""" - return DEVICE_PLATFORM.get(type(device), {}).get(domain, {}) # type: ignore[attr-defined] +def get_device_platform_groups(device: Device, platform: Platform) -> Iterable[int]: + """Return the list of device groups for a platform.""" + return get_device_platforms(device).get(platform, []) diff --git a/homeassistant/components/insteon/light.py b/homeassistant/components/insteon/light.py index 44574c696b4..1c12bc794f9 100644 --- a/homeassistant/components/insteon/light.py +++ b/homeassistant/components/insteon/light.py @@ -12,7 +12,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import SIGNAL_ADD_ENTITIES from .insteon_entity import InsteonEntity -from .utils import async_add_insteon_entities +from .utils import async_add_insteon_devices, async_add_insteon_entities MAX_BRIGHTNESS = 255 @@ -37,7 +37,12 @@ async def async_setup_entry( signal = f"{SIGNAL_ADD_ENTITIES}_{Platform.LIGHT}" async_dispatcher_connect(hass, signal, async_add_insteon_light_entities) - async_add_insteon_light_entities() + async_add_insteon_devices( + hass, + Platform.LIGHT, + InsteonDimmerEntity, + async_add_entities, + ) class InsteonDimmerEntity(InsteonEntity, LightEntity): diff --git a/homeassistant/components/insteon/lock.py b/homeassistant/components/insteon/lock.py index 75487e7696c..27fb0fd42d8 100644 --- a/homeassistant/components/insteon/lock.py +++ b/homeassistant/components/insteon/lock.py @@ -11,7 +11,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import SIGNAL_ADD_ENTITIES from .insteon_entity import InsteonEntity -from .utils import async_add_insteon_entities +from .utils import async_add_insteon_devices, async_add_insteon_entities async def async_setup_entry( @@ -30,7 +30,12 @@ async def async_setup_entry( signal = f"{SIGNAL_ADD_ENTITIES}_{Platform.LOCK}" async_dispatcher_connect(hass, signal, async_add_insteon_lock_entities) - async_add_insteon_lock_entities() + async_add_insteon_devices( + hass, + Platform.LOCK, + InsteonLockEntity, + async_add_entities, + ) class InsteonLockEntity(InsteonEntity, LockEntity): diff --git a/homeassistant/components/insteon/switch.py b/homeassistant/components/insteon/switch.py index 8f7c396f213..8acde0429cd 100644 --- a/homeassistant/components/insteon/switch.py +++ b/homeassistant/components/insteon/switch.py @@ -10,7 +10,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import SIGNAL_ADD_ENTITIES from .insteon_entity import InsteonEntity -from .utils import async_add_insteon_entities +from .utils import async_add_insteon_devices, async_add_insteon_entities async def async_setup_entry( @@ -33,7 +33,12 @@ async def async_setup_entry( signal = f"{SIGNAL_ADD_ENTITIES}_{Platform.SWITCH}" async_dispatcher_connect(hass, signal, async_add_insteon_switch_entities) - async_add_insteon_switch_entities() + async_add_insteon_devices( + hass, + Platform.SWITCH, + InsteonSwitchEntity, + async_add_entities, + ) class InsteonSwitchEntity(InsteonEntity, SwitchEntity): diff --git a/homeassistant/components/insteon/utils.py b/homeassistant/components/insteon/utils.py index 58b2430092c..2ef9913ab8c 100644 --- a/homeassistant/components/insteon/utils.py +++ b/homeassistant/components/insteon/utils.py @@ -1,7 +1,10 @@ """Utilities used by insteon component.""" +from __future__ import annotations + import asyncio from collections.abc import Callable import logging +from typing import TYPE_CHECKING, Any from pyinsteon import devices from pyinsteon.address import Address @@ -30,6 +33,7 @@ from homeassistant.const import ( CONF_ENTITY_ID, CONF_PLATFORM, ENTITY_MATCH_ALL, + Platform, ) from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.helpers import device_registry as dr @@ -38,6 +42,7 @@ from homeassistant.helpers.dispatcher import ( async_dispatcher_send, dispatcher_send, ) +from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( CONF_CAT, @@ -78,7 +83,7 @@ from .const import ( SRV_X10_ALL_LIGHTS_ON, SRV_X10_ALL_UNITS_OFF, ) -from .ipdb import get_device_platforms, get_platform_groups +from .ipdb import get_device_platform_groups, get_device_platforms from .schemas import ( ADD_ALL_LINK_SCHEMA, ADD_DEFAULT_LINKS_SCHEMA, @@ -89,6 +94,9 @@ from .schemas import ( X10_HOUSECODE_SCHEMA, ) +if TYPE_CHECKING: + from .insteon_entity import InsteonEntity + _LOGGER = logging.getLogger(__name__) @@ -160,6 +168,7 @@ def register_new_device_callback(hass): for platform in platforms: signal = f"{SIGNAL_ADD_ENTITIES}_{platform}" dispatcher_send(hass, signal, {"address": device.address}) + add_insteon_events(hass, device) devices.subscribe(async_new_insteon_device, force_strong_ref=True) @@ -383,20 +392,38 @@ def print_aldb_to_log(aldb): @callback def async_add_insteon_entities( - hass, platform, entity_type, async_add_entities, discovery_info -): - """Add Insteon devices to a platform.""" - new_entities = [] - device_list = [discovery_info.get("address")] if discovery_info else devices - - for address in device_list: - device = devices[address] - groups = get_platform_groups(device, platform) - for group in groups: - new_entities.append(entity_type(device, group)) + hass: HomeAssistant, + platform: Platform, + entity_type: type[InsteonEntity], + async_add_entities: AddEntitiesCallback, + discovery_info: dict[str, Any], +) -> None: + """Add an Insteon group to a platform.""" + address = discovery_info["address"] + device = devices[address] + new_entities = [ + entity_type(device=device, group=group) for group in discovery_info["groups"] + ] async_add_entities(new_entities) +@callback +def async_add_insteon_devices( + hass: HomeAssistant, + platform: Platform, + entity_type: type[InsteonEntity], + async_add_entities: AddEntitiesCallback, +) -> None: + """Add all entities to a platform.""" + for address in devices: + device = devices[address] + groups = get_device_platform_groups(device, platform) + discovery_info = {"address": address, "groups": groups} + async_add_insteon_entities( + hass, platform, entity_type, async_add_entities, discovery_info + ) + + def get_usb_ports() -> dict[str, str]: """Return a dict of USB ports and their friendly names.""" ports = list_ports.comports() From d745b4418070f1882bc5c75e2b86a154c5d4f28c Mon Sep 17 00:00:00 2001 From: Tom Harris Date: Thu, 8 Jun 2023 12:36:42 -0400 Subject: [PATCH 24/83] Fix Insteon startup for users with X10 devices (#94277) --- homeassistant/components/insteon/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/insteon/utils.py b/homeassistant/components/insteon/utils.py index 2ef9913ab8c..f9c22ef62a5 100644 --- a/homeassistant/components/insteon/utils.py +++ b/homeassistant/components/insteon/utils.py @@ -140,6 +140,9 @@ def add_insteon_events(hass: HomeAssistant, device: Device) -> None: _LOGGER.debug("Firing event %s with %s", event, schema) hass.bus.async_fire(event, schema) + if str(device.address).startswith("X10"): + return + for name_or_group, event in device.events.items(): if isinstance(name_or_group, int): for _, event in device.events[name_or_group].items(): @@ -166,8 +169,9 @@ def register_new_device_callback(hass): await device.async_status() platforms = get_device_platforms(device) for platform in platforms: + groups = get_device_platform_groups(device, platform) signal = f"{SIGNAL_ADD_ENTITIES}_{platform}" - dispatcher_send(hass, signal, {"address": device.address}) + dispatcher_send(hass, signal, {"address": device.address, "groups": groups}) add_insteon_events(hass, device) devices.subscribe(async_new_insteon_device, force_strong_ref=True) From d9919707547cf6059974d4ffa7387e972412d91e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 8 Jun 2023 13:39:33 -0400 Subject: [PATCH 25/83] Bumped version to 2023.6.1 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 9308d364ecb..3392b6dabd8 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -8,7 +8,7 @@ from .backports.enum import StrEnum APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2023 MINOR_VERSION: Final = 6 -PATCH_VERSION: Final = "0" +PATCH_VERSION: Final = "1" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 10, 0) diff --git a/pyproject.toml b/pyproject.toml index 2ece07d96e6..ca4f61a5ccc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2023.6.0" +version = "2023.6.1" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 95528f875e41ff9d5ee16bfefd446b76639f1bbb Mon Sep 17 00:00:00 2001 From: FFT Date: Tue, 13 Jun 2023 14:46:58 +0800 Subject: [PATCH 26/83] Change pyoppleio to pyoppleio-legacy (#88050) * Change pyoppleio to pyoppleio-310 (#75268) * [m] change opple component's dependency to a new working one --- homeassistant/components/opple/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/opple/manifest.json b/homeassistant/components/opple/manifest.json index 9d87114c2d0..174907dfd0f 100644 --- a/homeassistant/components/opple/manifest.json +++ b/homeassistant/components/opple/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/opple", "iot_class": "local_polling", "loggers": ["pyoppleio"], - "requirements": ["pyoppleio==1.0.5"] + "requirements": ["pyoppleio-legacy==1.0.8"] } diff --git a/requirements_all.txt b/requirements_all.txt index eca5516f7ec..2afad933fcd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1861,7 +1861,7 @@ pyopenuv==2023.02.0 pyopnsense==0.2.0 # homeassistant.components.opple -pyoppleio==1.0.5 +pyoppleio-legacy==1.0.8 # homeassistant.components.opentherm_gw pyotgw==2.1.3 From 427f0f4beed21c3995fd560c97355c6a18476f6d Mon Sep 17 00:00:00 2001 From: Tom Harris Date: Sun, 11 Jun 2023 10:21:33 -0400 Subject: [PATCH 27/83] Fix issue with Insteon linked devices maintaining current state (#94286) * Bump pyinsteon * Update tests --- homeassistant/components/insteon/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/insteon/mock_devices.py | 4 ++-- tests/components/insteon/test_api_properties.py | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/insteon/manifest.json b/homeassistant/components/insteon/manifest.json index cc8495384b1..ad3fb7bfbe8 100644 --- a/homeassistant/components/insteon/manifest.json +++ b/homeassistant/components/insteon/manifest.json @@ -17,7 +17,7 @@ "iot_class": "local_push", "loggers": ["pyinsteon", "pypubsub"], "requirements": [ - "pyinsteon==1.4.2", + "pyinsteon==1.4.3", "insteon-frontend-home-assistant==0.3.5" ], "usb": [ diff --git a/requirements_all.txt b/requirements_all.txt index 2afad933fcd..b8348d1c005 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1699,7 +1699,7 @@ pyialarm==2.2.0 pyicloud==1.0.0 # homeassistant.components.insteon -pyinsteon==1.4.2 +pyinsteon==1.4.3 # homeassistant.components.intesishome pyintesishome==1.8.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7f48debc62b..dcd24f6f751 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1248,7 +1248,7 @@ pyialarm==2.2.0 pyicloud==1.0.0 # homeassistant.components.insteon -pyinsteon==1.4.2 +pyinsteon==1.4.3 # homeassistant.components.ipma pyipma==3.0.6 diff --git a/tests/components/insteon/mock_devices.py b/tests/components/insteon/mock_devices.py index dd0ab0b56a0..dea9fb4e34f 100644 --- a/tests/components/insteon/mock_devices.py +++ b/tests/components/insteon/mock_devices.py @@ -151,11 +151,11 @@ class MockDevices: for flag in operating_flags: value = operating_flags[flag] if device.operating_flags.get(flag): - device.operating_flags[flag].load(value) + device.operating_flags[flag].set_value(value) for flag in properties: value = properties[flag] if device.properties.get(flag): - device.properties[flag].load(value) + device.properties[flag].set_value(value) async def async_add_device(self, address=None, multiple=False): """Mock the async_add_device method.""" diff --git a/tests/components/insteon/test_api_properties.py b/tests/components/insteon/test_api_properties.py index a667e2144d0..850ccc85411 100644 --- a/tests/components/insteon/test_api_properties.py +++ b/tests/components/insteon/test_api_properties.py @@ -119,7 +119,7 @@ async def test_get_read_only_properties( mock_read_only = ExtendedProperty( "44.44.44", "mock_read_only", bool, is_read_only=True ) - mock_read_only.load(False) + mock_read_only.set_value(False) ws_client, devices = await _setup( hass, hass_ws_client, "44.44.44", iolinc_properties_data @@ -368,7 +368,7 @@ async def test_change_float_property( ) device = devices["44.44.44"] delay_prop = device.configuration[MOMENTARY_DELAY] - delay_prop.load(0) + delay_prop.set_value(0) with patch.object(insteon.api.properties, "devices", devices): await ws_client.send_json( { From 9c8444da0e77d551002f39c77e58a4859b1dbe8b Mon Sep 17 00:00:00 2001 From: Glenn Waters Date: Sun, 11 Jun 2023 21:25:01 -0400 Subject: [PATCH 28/83] Bump elkm1-lib to 2.2.5 (#94296) Co-authored-by: J. Nick Koston --- homeassistant/components/elkm1/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/elkm1/manifest.json b/homeassistant/components/elkm1/manifest.json index d7094a2e60b..ccac1593fa0 100644 --- a/homeassistant/components/elkm1/manifest.json +++ b/homeassistant/components/elkm1/manifest.json @@ -15,5 +15,5 @@ "documentation": "https://www.home-assistant.io/integrations/elkm1", "iot_class": "local_push", "loggers": ["elkm1_lib"], - "requirements": ["elkm1-lib==2.2.2"] + "requirements": ["elkm1-lib==2.2.5"] } diff --git a/requirements_all.txt b/requirements_all.txt index b8348d1c005..0d88b5127ac 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -650,7 +650,7 @@ elgato==4.0.1 eliqonline==1.2.2 # homeassistant.components.elkm1 -elkm1-lib==2.2.2 +elkm1-lib==2.2.5 # homeassistant.components.elmax elmax_api==0.0.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index dcd24f6f751..7e44c97b171 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -515,7 +515,7 @@ easyenergy==0.3.0 elgato==4.0.1 # homeassistant.components.elkm1 -elkm1-lib==2.2.2 +elkm1-lib==2.2.5 # homeassistant.components.elmax elmax_api==0.0.4 From e091793b6cefd4bd58840d4f374ca1ae28a9a649 Mon Sep 17 00:00:00 2001 From: Nathan Spencer Date: Tue, 13 Jun 2023 03:51:46 -0600 Subject: [PATCH 29/83] Bump pylitterbot to 2023.4.2 (#94301) --- homeassistant/components/litterrobot/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/litterrobot/manifest.json b/homeassistant/components/litterrobot/manifest.json index d3dcf77f324..2a4a3447eb6 100644 --- a/homeassistant/components/litterrobot/manifest.json +++ b/homeassistant/components/litterrobot/manifest.json @@ -12,5 +12,5 @@ "integration_type": "hub", "iot_class": "cloud_push", "loggers": ["pylitterbot"], - "requirements": ["pylitterbot==2023.4.0"] + "requirements": ["pylitterbot==2023.4.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 0d88b5127ac..ede0ea411bc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1771,7 +1771,7 @@ pylibrespot-java==0.1.1 pylitejet==0.5.0 # homeassistant.components.litterrobot -pylitterbot==2023.4.0 +pylitterbot==2023.4.2 # homeassistant.components.lutron_caseta pylutron-caseta==0.18.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7e44c97b171..741d5897a14 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1302,7 +1302,7 @@ pylibrespot-java==0.1.1 pylitejet==0.5.0 # homeassistant.components.litterrobot -pylitterbot==2023.4.0 +pylitterbot==2023.4.2 # homeassistant.components.lutron_caseta pylutron-caseta==0.18.1 From 3434d749937d14f748bcaab95cb4e4a8ec7798ce Mon Sep 17 00:00:00 2001 From: Jonathan Keljo Date: Fri, 9 Jun 2023 00:39:14 -0700 Subject: [PATCH 30/83] Upgrade sisyphus-control to 3.1.3 (#94310) --- homeassistant/components/sisyphus/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sisyphus/manifest.json b/homeassistant/components/sisyphus/manifest.json index 1a8d9e2e16b..dbb40344d66 100644 --- a/homeassistant/components/sisyphus/manifest.json +++ b/homeassistant/components/sisyphus/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/sisyphus", "iot_class": "local_push", "loggers": ["sisyphus_control"], - "requirements": ["sisyphus-control==3.1.2"] + "requirements": ["sisyphus-control==3.1.3"] } diff --git a/requirements_all.txt b/requirements_all.txt index ede0ea411bc..f6ec7496d6a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2367,7 +2367,7 @@ simplepush==2.1.1 simplisafe-python==2023.05.0 # homeassistant.components.sisyphus -sisyphus-control==3.1.2 +sisyphus-control==3.1.3 # homeassistant.components.slack slackclient==2.5.0 From 6029e23ab770319cf61037ad58839a437bed7b54 Mon Sep 17 00:00:00 2001 From: Jafar Atili Date: Fri, 9 Jun 2023 21:54:11 +0300 Subject: [PATCH 31/83] fix: electrasmart - cast temperature to int in set_temperature (#94368) fix: cast temperature to int --- homeassistant/components/electrasmart/climate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/electrasmart/climate.py b/homeassistant/components/electrasmart/climate.py index 361f906133d..a9688939048 100644 --- a/homeassistant/components/electrasmart/climate.py +++ b/homeassistant/components/electrasmart/climate.py @@ -250,7 +250,7 @@ class ElectraClimateEntity(ClimateEntity): if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None: raise ValueError("No target temperature provided") - self._electra_ac_device.set_temperature(temperature) + self._electra_ac_device.set_temperature(int(temperature)) await self._async_operate_electra_ac() def _update_device_attrs(self) -> None: From 96cb5ff8b0d29d98ad2e386026d3baa22146b831 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Sun, 11 Jun 2023 16:21:06 +0200 Subject: [PATCH 32/83] Fix dep noaa-coops for noaa_tides (#94370) Bump noaa-coops to 0.1.9 --- homeassistant/components/noaa_tides/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/noaa_tides/manifest.json b/homeassistant/components/noaa_tides/manifest.json index 7b954153cf1..85c6fbcb788 100644 --- a/homeassistant/components/noaa_tides/manifest.json +++ b/homeassistant/components/noaa_tides/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/noaa_tides", "iot_class": "cloud_polling", "loggers": ["noaa_coops"], - "requirements": ["noaa-coops==0.1.8"] + "requirements": ["noaa-coops==0.1.9"] } diff --git a/requirements_all.txt b/requirements_all.txt index f6ec7496d6a..74a8df65c03 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1218,7 +1218,7 @@ niko-home-control==0.2.1 niluclient==0.1.2 # homeassistant.components.noaa_tides -noaa-coops==0.1.8 +noaa-coops==0.1.9 # homeassistant.components.nfandroidtv notifications-android-tv==0.1.5 From 582fd11a7037b09fb3fb30ab49d839a964fae3bb Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Sun, 11 Jun 2023 20:01:41 +0200 Subject: [PATCH 33/83] Fix deprecated asyncio.wait use with coroutines (#94371) --- homeassistant/components/xiaomi_miio/fan.py | 4 +--- homeassistant/components/xiaomi_miio/light.py | 4 +++- homeassistant/components/xiaomi_miio/switch.py | 4 +++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index aaf471518d8..247b91d1b06 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -275,9 +275,7 @@ async def async_setup_entry( if not entity_method: continue await entity_method(**params) - update_tasks.append( - hass.async_create_task(entity.async_update_ha_state(True)) - ) + update_tasks.append(asyncio.create_task(entity.async_update_ha_state(True))) if update_tasks: await asyncio.wait(update_tasks) diff --git a/homeassistant/components/xiaomi_miio/light.py b/homeassistant/components/xiaomi_miio/light.py index ed1bdef9e33..9b8357a534f 100644 --- a/homeassistant/components/xiaomi_miio/light.py +++ b/homeassistant/components/xiaomi_miio/light.py @@ -229,7 +229,9 @@ async def async_setup_entry( if not hasattr(target_device, method["method"]): continue await getattr(target_device, method["method"])(**params) - update_tasks.append(target_device.async_update_ha_state(True)) + update_tasks.append( + asyncio.create_task(target_device.async_update_ha_state(True)) + ) if update_tasks: await asyncio.wait(update_tasks) diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index 08b15f58217..9bba9f61123 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -500,7 +500,9 @@ async def async_setup_other_entry(hass, config_entry, async_add_entities): if not hasattr(device, method["method"]): continue await getattr(device, method["method"])(**params) - update_tasks.append(device.async_update_ha_state(True)) + update_tasks.append( + asyncio.create_task(device.async_update_ha_state(True)) + ) if update_tasks: await asyncio.wait(update_tasks) From 4dbc40869639acb141f1397d1ac35faf194f80e1 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Sat, 10 Jun 2023 10:41:51 +0200 Subject: [PATCH 34/83] Update xknxproject to 3.1.1 (#94375) --- homeassistant/components/knx/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index ba706c756cb..61defa64e22 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -12,7 +12,7 @@ "quality_scale": "platinum", "requirements": [ "xknx==2.10.0", - "xknxproject==3.1.0", + "xknxproject==3.1.1", "knx_frontend==2023.5.31.141540" ] } diff --git a/requirements_all.txt b/requirements_all.txt index 74a8df65c03..2798c50cc4b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2680,7 +2680,7 @@ xiaomi-ble==0.17.2 xknx==2.10.0 # homeassistant.components.knx -xknxproject==3.1.0 +xknxproject==3.1.1 # homeassistant.components.bluesound # homeassistant.components.fritz diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 741d5897a14..7a3b9058373 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1953,7 +1953,7 @@ xiaomi-ble==0.17.2 xknx==2.10.0 # homeassistant.components.knx -xknxproject==3.1.0 +xknxproject==3.1.1 # homeassistant.components.bluesound # homeassistant.components.fritz From 2c7a1765806fd226d0276144f6f370761cbc6cf8 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Sun, 11 Jun 2023 02:35:52 -0400 Subject: [PATCH 35/83] Re-add event listeners after Z-Wave server disconnection (#94383) * Re-add event listeners after Z-Wave server disconnection * switch order * Add tests --- homeassistant/components/zwave_js/__init__.py | 3 + .../components/zwave_js/triggers/event.py | 67 ++++++++----- .../zwave_js/triggers/value_updated.py | 61 ++++++++---- tests/components/zwave_js/test_trigger.py | 98 +++++++++++++++++++ 4 files changed, 187 insertions(+), 42 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index a89d20d8384..814e3e86a7a 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -209,6 +209,9 @@ async def start_client( LOGGER.info("Connection to Zwave JS Server initialized") assert client.driver + async_dispatcher_send( + hass, f"{DOMAIN}_{client.driver.controller.home_id}_connected_to_server" + ) await driver_events.setup(client.driver) diff --git a/homeassistant/components/zwave_js/triggers/event.py b/homeassistant/components/zwave_js/triggers/event.py index 12c9d267ca6..32bd3130e03 100644 --- a/homeassistant/components/zwave_js/triggers/event.py +++ b/homeassistant/components/zwave_js/triggers/event.py @@ -1,18 +1,20 @@ """Offer Z-Wave JS event listening automation trigger.""" from __future__ import annotations +from collections.abc import Callable import functools from pydantic import ValidationError import voluptuous as vol from zwave_js_server.client import Client from zwave_js_server.model.controller import CONTROLLER_EVENT_MODEL_MAP -from zwave_js_server.model.driver import DRIVER_EVENT_MODEL_MAP +from zwave_js_server.model.driver import DRIVER_EVENT_MODEL_MAP, Driver from zwave_js_server.model.node import NODE_EVENT_MODEL_MAP from homeassistant.const import ATTR_DEVICE_ID, ATTR_ENTITY_ID, CONF_PLATFORM from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback from homeassistant.helpers import config_validation as cv, device_registry as dr +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo from homeassistant.helpers.typing import ConfigType @@ -150,7 +152,7 @@ async def async_attach_trigger( event_name = config[ATTR_EVENT] event_data_filter = config.get(ATTR_EVENT_DATA, {}) - unsubs = [] + unsubs: list[Callable] = [] job = HassJob(action) trigger_data = trigger_info["trigger_data"] @@ -199,26 +201,6 @@ async def async_attach_trigger( hass.async_run_hass_job(job, {"trigger": payload}) - if not nodes: - entry_id = config[ATTR_CONFIG_ENTRY_ID] - client: Client = hass.data[DOMAIN][entry_id][DATA_CLIENT] - assert client.driver - if event_source == "controller": - unsubs.append(client.driver.controller.on(event_name, async_on_event)) - else: - unsubs.append(client.driver.on(event_name, async_on_event)) - - for node in nodes: - driver = node.client.driver - assert driver is not None # The node comes from the driver. - device_identifier = get_device_id(driver, node) - device = dev_reg.async_get_device({device_identifier}) - assert device - # We need to store the device for the callback - unsubs.append( - node.on(event_name, functools.partial(async_on_event, device=device)) - ) - @callback def async_remove() -> None: """Remove state listeners async.""" @@ -226,4 +208,45 @@ async def async_attach_trigger( unsub() unsubs.clear() + @callback + def _create_zwave_listeners() -> None: + """Create Z-Wave JS listeners.""" + async_remove() + # Nodes list can come from different drivers and we will need to listen to + # server connections for all of them. + drivers: set[Driver] = set() + if not nodes: + entry_id = config[ATTR_CONFIG_ENTRY_ID] + client: Client = hass.data[DOMAIN][entry_id][DATA_CLIENT] + driver = client.driver + assert driver + drivers.add(driver) + if event_source == "controller": + unsubs.append(driver.controller.on(event_name, async_on_event)) + else: + unsubs.append(driver.on(event_name, async_on_event)) + + for node in nodes: + driver = node.client.driver + assert driver is not None # The node comes from the driver. + drivers.add(driver) + device_identifier = get_device_id(driver, node) + device = dev_reg.async_get_device({device_identifier}) + assert device + # We need to store the device for the callback + unsubs.append( + node.on(event_name, functools.partial(async_on_event, device=device)) + ) + + for driver in drivers: + unsubs.append( + async_dispatcher_connect( + hass, + f"{DOMAIN}_{driver.controller.home_id}_connected_to_server", + _create_zwave_listeners, + ) + ) + + _create_zwave_listeners() + return async_remove diff --git a/homeassistant/components/zwave_js/triggers/value_updated.py b/homeassistant/components/zwave_js/triggers/value_updated.py index 655d1f9070e..4e21774c98f 100644 --- a/homeassistant/components/zwave_js/triggers/value_updated.py +++ b/homeassistant/components/zwave_js/triggers/value_updated.py @@ -1,15 +1,18 @@ """Offer Z-Wave JS value updated listening automation trigger.""" from __future__ import annotations +from collections.abc import Callable import functools import voluptuous as vol from zwave_js_server.const import CommandClass +from zwave_js_server.model.driver import Driver from zwave_js_server.model.value import Value, get_value_id_str from homeassistant.const import ATTR_DEVICE_ID, ATTR_ENTITY_ID, CONF_PLATFORM, MATCH_ALL from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback from homeassistant.helpers import config_validation as cv, device_registry as dr +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo from homeassistant.helpers.typing import ConfigType @@ -99,7 +102,7 @@ async def async_attach_trigger( property_ = config[ATTR_PROPERTY] endpoint = config.get(ATTR_ENDPOINT) property_key = config.get(ATTR_PROPERTY_KEY) - unsubs = [] + unsubs: list[Callable] = [] job = HassJob(action) trigger_data = trigger_info["trigger_data"] @@ -153,29 +156,11 @@ async def async_attach_trigger( ATTR_PREVIOUS_VALUE_RAW: prev_value_raw, ATTR_CURRENT_VALUE: curr_value, ATTR_CURRENT_VALUE_RAW: curr_value_raw, - "description": f"Z-Wave value {value_id} updated on {device_name}", + "description": f"Z-Wave value {value.value_id} updated on {device_name}", } hass.async_run_hass_job(job, {"trigger": payload}) - for node in nodes: - driver = node.client.driver - assert driver is not None # The node comes from the driver. - device_identifier = get_device_id(driver, node) - device = dev_reg.async_get_device({device_identifier}) - assert device - value_id = get_value_id_str( - node, command_class, property_, endpoint, property_key - ) - value = node.values[value_id] - # We need to store the current value and device for the callback - unsubs.append( - node.on( - "value updated", - functools.partial(async_on_value_updated, value, device), - ) - ) - @callback def async_remove() -> None: """Remove state listeners async.""" @@ -183,4 +168,40 @@ async def async_attach_trigger( unsub() unsubs.clear() + def _create_zwave_listeners() -> None: + """Create Z-Wave JS listeners.""" + async_remove() + # Nodes list can come from different drivers and we will need to listen to + # server connections for all of them. + drivers: set[Driver] = set() + for node in nodes: + driver = node.client.driver + assert driver is not None # The node comes from the driver. + drivers.add(driver) + device_identifier = get_device_id(driver, node) + device = dev_reg.async_get_device({device_identifier}) + assert device + value_id = get_value_id_str( + node, command_class, property_, endpoint, property_key + ) + value = node.values[value_id] + # We need to store the current value and device for the callback + unsubs.append( + node.on( + "value updated", + functools.partial(async_on_value_updated, value, device), + ) + ) + + for driver in drivers: + unsubs.append( + async_dispatcher_connect( + hass, + f"{DOMAIN}_{driver.controller.home_id}_connected_to_server", + _create_zwave_listeners, + ) + ) + + _create_zwave_listeners() + return async_remove diff --git a/tests/components/zwave_js/test_trigger.py b/tests/components/zwave_js/test_trigger.py index 9df8aa75f43..0fb3b829d9a 100644 --- a/tests/components/zwave_js/test_trigger.py +++ b/tests/components/zwave_js/test_trigger.py @@ -1109,3 +1109,101 @@ def test_get_trigger_platform_failure() -> None: """Test _get_trigger_platform.""" with pytest.raises(ValueError): _get_trigger_platform({CONF_PLATFORM: "zwave_js.invalid"}) + + +async def test_server_reconnect_event( + hass: HomeAssistant, client, lock_schlage_be469, integration +) -> None: + """Test that when we reconnect to server, event triggers reattach.""" + trigger_type = f"{DOMAIN}.event" + node: Node = lock_schlage_be469 + dev_reg = async_get_dev_reg(hass) + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device + + event_name = "interview stage completed" + + original_len = len(node._listeners.get(event_name, [])) + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": trigger_type, + "entity_id": SCHLAGE_BE469_LOCK_ENTITY, + "event_source": "node", + "event": event_name, + }, + "action": { + "event": "blah", + }, + }, + ] + }, + ) + + assert len(node._listeners.get(event_name, [])) == original_len + 1 + old_listener = node._listeners.get(event_name, [])[original_len] + + await hass.config_entries.async_reload(integration.entry_id) + await hass.async_block_till_done() + + # Make sure there is still a listener added for the trigger + assert len(node._listeners.get(event_name, [])) == original_len + 1 + + # Make sure the old listener was removed + assert old_listener not in node._listeners.get(event_name, []) + + +async def test_server_reconnect_value_updated( + hass: HomeAssistant, client, lock_schlage_be469, integration +) -> None: + """Test that when we reconnect to server, value_updated triggers reattach.""" + trigger_type = f"{DOMAIN}.value_updated" + node: Node = lock_schlage_be469 + dev_reg = async_get_dev_reg(hass) + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device + + event_name = "value updated" + + original_len = len(node._listeners.get(event_name, [])) + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": trigger_type, + "entity_id": SCHLAGE_BE469_LOCK_ENTITY, + "command_class": CommandClass.DOOR_LOCK.value, + "property": "latchStatus", + }, + "action": { + "event": "no_value_filter", + }, + }, + ] + }, + ) + + assert len(node._listeners.get(event_name, [])) == original_len + 1 + old_listener = node._listeners.get(event_name, [])[original_len] + + await hass.config_entries.async_reload(integration.entry_id) + await hass.async_block_till_done() + + # Make sure there is still a listener added for the trigger + assert len(node._listeners.get(event_name, [])) == original_len + 1 + + # Make sure the old listener was removed + assert old_listener not in node._listeners.get(event_name, []) From b2db84979814fd32cf01bb710fcbb133413d636c Mon Sep 17 00:00:00 2001 From: jasonkuster Date: Mon, 12 Jun 2023 10:07:42 -0700 Subject: [PATCH 36/83] Fix ZHA binding api to actually return responses (#94388) --- homeassistant/components/zha/websocket_api.py | 13 ++- tests/components/zha/test_websocket_api.py | 94 ++++++++++++++++++- 2 files changed, 104 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zha/websocket_api.py b/homeassistant/components/zha/websocket_api.py index 28e115c0ec4..97862bd36f0 100644 --- a/homeassistant/components/zha/websocket_api.py +++ b/homeassistant/components/zha/websocket_api.py @@ -907,6 +907,7 @@ async def websocket_bind_devices( ATTR_TARGET_IEEE, target_ieee, ) + connection.send_result(msg[ID]) @websocket_api.require_admin @@ -935,6 +936,7 @@ async def websocket_unbind_devices( ATTR_TARGET_IEEE, target_ieee, ) + connection.send_result(msg[ID]) @websocket_api.require_admin @@ -951,13 +953,14 @@ async def websocket_bind_group( hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] ) -> None: """Directly bind a device to a group.""" - zha_gateway: ZHAGateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] + zha_gateway: ZHAGateway = get_gateway(hass) source_ieee: EUI64 = msg[ATTR_SOURCE_IEEE] group_id: int = msg[GROUP_ID] bindings: list[ClusterBinding] = msg[BINDINGS] source_device = zha_gateway.get_device(source_ieee) assert source_device await source_device.async_bind_to_group(group_id, bindings) + connection.send_result(msg[ID]) @websocket_api.require_admin @@ -974,13 +977,19 @@ async def websocket_unbind_group( hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] ) -> None: """Unbind a device from a group.""" - zha_gateway: ZHAGateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] + zha_gateway: ZHAGateway = get_gateway(hass) source_ieee: EUI64 = msg[ATTR_SOURCE_IEEE] group_id: int = msg[GROUP_ID] bindings: list[ClusterBinding] = msg[BINDINGS] source_device = zha_gateway.get_device(source_ieee) assert source_device await source_device.async_unbind_from_group(group_id, bindings) + connection.send_result(msg[ID]) + + +def get_gateway(hass: HomeAssistant) -> ZHAGateway: + """Return Gateway, mainly as fixture for mocking during testing.""" + return hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] async def async_binding_operation( diff --git a/tests/components/zha/test_websocket_api.py b/tests/components/zha/test_websocket_api.py index 5250b62a9b0..0904fc1f685 100644 --- a/tests/components/zha/test_websocket_api.py +++ b/tests/components/zha/test_websocket_api.py @@ -4,15 +4,17 @@ from __future__ import annotations from binascii import unhexlify from copy import deepcopy from typing import TYPE_CHECKING -from unittest.mock import ANY, AsyncMock, call, patch +from unittest.mock import ANY, AsyncMock, MagicMock, call, patch import pytest import voluptuous as vol import zigpy.backups import zigpy.profiles.zha import zigpy.types +from zigpy.types.named import EUI64 import zigpy.zcl.clusters.general as general import zigpy.zcl.clusters.security as security +import zigpy.zdo.types as zdo_types from homeassistant.components.websocket_api import const from homeassistant.components.zha import DOMAIN @@ -26,6 +28,8 @@ from homeassistant.components.zha.core.const import ( ATTR_MODEL, ATTR_NEIGHBORS, ATTR_QUIRK_APPLIED, + ATTR_TYPE, + BINDINGS, CLUSTER_TYPE_IN, EZSP_OVERWRITE_EUI64, GROUP_ID, @@ -37,6 +41,7 @@ from homeassistant.components.zha.websocket_api import ( ATTR_INSTALL_CODE, ATTR_QR_CODE, ATTR_SOURCE_IEEE, + ATTR_TARGET_IEEE, ID, SERVICE_PERMIT, TYPE, @@ -884,3 +889,90 @@ async def test_websocket_change_channel( assert msg["success"] change_channel_mock.mock_calls == [call(ANY, new_channel)] + + +@pytest.mark.parametrize( + "operation", + [("bind", zdo_types.ZDOCmd.Bind_req), ("unbind", zdo_types.ZDOCmd.Unbind_req)], +) +async def test_websocket_bind_unbind_devices( + operation: tuple[str, zdo_types.ZDOCmd], + app_controller: ControllerApplication, + zha_client, +) -> None: + """Test websocket API for binding and unbinding devices to devices.""" + + command_type, req = operation + with patch( + "homeassistant.components.zha.websocket_api.async_binding_operation", + autospec=True, + ) as binding_operation_mock: + await zha_client.send_json( + { + ID: 27, + TYPE: f"zha/devices/{command_type}", + ATTR_SOURCE_IEEE: IEEE_SWITCH_DEVICE, + ATTR_TARGET_IEEE: IEEE_GROUPABLE_DEVICE, + } + ) + msg = await zha_client.receive_json() + + assert msg["id"] == 27 + assert msg["type"] == const.TYPE_RESULT + assert msg["success"] + assert binding_operation_mock.mock_calls == [ + call( + ANY, + EUI64.convert(IEEE_SWITCH_DEVICE), + EUI64.convert(IEEE_GROUPABLE_DEVICE), + req, + ) + ] + + +@pytest.mark.parametrize("command_type", ["bind", "unbind"]) +async def test_websocket_bind_unbind_group( + command_type: str, + app_controller: ControllerApplication, + zha_client, +) -> None: + """Test websocket API for binding and unbinding devices to groups.""" + + test_group_id = 0x0001 + gateway_mock = MagicMock() + with patch( + "homeassistant.components.zha.websocket_api.get_gateway", + return_value=gateway_mock, + ): + device_mock = MagicMock() + bind_mock = AsyncMock() + unbind_mock = AsyncMock() + device_mock.async_bind_to_group = bind_mock + device_mock.async_unbind_from_group = unbind_mock + gateway_mock.get_device = MagicMock() + gateway_mock.get_device.return_value = device_mock + await zha_client.send_json( + { + ID: 27, + TYPE: f"zha/groups/{command_type}", + ATTR_SOURCE_IEEE: IEEE_SWITCH_DEVICE, + GROUP_ID: test_group_id, + BINDINGS: [ + { + ATTR_ENDPOINT_ID: 1, + ID: 6, + ATTR_NAME: "OnOff", + ATTR_TYPE: "out", + }, + ], + } + ) + msg = await zha_client.receive_json() + + assert msg["id"] == 27 + assert msg["type"] == const.TYPE_RESULT + assert msg["success"] + if command_type == "bind": + assert bind_mock.mock_calls == [call(test_group_id, ANY)] + elif command_type == "unbind": + assert unbind_mock.mock_calls == [call(test_group_id, ANY)] From ac00977e57665b056c2ac6a2465a212d11e96dd4 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 11 Jun 2023 12:30:38 +0200 Subject: [PATCH 37/83] Abort youtube configuration if user has no channel (#94402) * Abort configuration if user has no channel * Clean up --------- Co-authored-by: Martin Hjelmare --- .../components/youtube/config_flow.py | 13 +++++- homeassistant/components/youtube/const.py | 1 + homeassistant/components/youtube/strings.json | 1 + .../youtube/fixtures/get_no_channel.json | 9 +++++ tests/components/youtube/test_config_flow.py | 40 +++++++++++++++++++ 5 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 tests/components/youtube/fixtures/get_no_channel.json diff --git a/homeassistant/components/youtube/config_flow.py b/homeassistant/components/youtube/config_flow.py index a2adebc84af..92695f80a2e 100644 --- a/homeassistant/components/youtube/config_flow.py +++ b/homeassistant/components/youtube/config_flow.py @@ -22,7 +22,13 @@ from homeassistant.helpers.selector import ( SelectSelectorConfig, ) -from .const import CONF_CHANNELS, DEFAULT_ACCESS, DOMAIN, LOGGER +from .const import ( + CHANNEL_CREATION_HELP_URL, + CONF_CHANNELS, + DEFAULT_ACCESS, + DOMAIN, + LOGGER, +) async def get_resource(hass: HomeAssistant, token: str) -> Resource: @@ -99,6 +105,11 @@ class OAuth2FlowHandler( response = await self.hass.async_add_executor_job( own_channel_request.execute ) + if not response["items"]: + return self.async_abort( + reason="no_channel", + description_placeholders={"support_url": CHANNEL_CREATION_HELP_URL}, + ) own_channel = response["items"][0] except HttpError as ex: error = ex.reason diff --git a/homeassistant/components/youtube/const.py b/homeassistant/components/youtube/const.py index e2757e3856d..7404cd04665 100644 --- a/homeassistant/components/youtube/const.py +++ b/homeassistant/components/youtube/const.py @@ -4,6 +4,7 @@ import logging DEFAULT_ACCESS = ["https://www.googleapis.com/auth/youtube.readonly"] DOMAIN = "youtube" MANUFACTURER = "Google, Inc." +CHANNEL_CREATION_HELP_URL = "https://support.google.com/youtube/answer/1646861" CONF_CHANNELS = "channels" CONF_ID = "id" diff --git a/homeassistant/components/youtube/strings.json b/homeassistant/components/youtube/strings.json index 24369ab26f9..eb89738708e 100644 --- a/homeassistant/components/youtube/strings.json +++ b/homeassistant/components/youtube/strings.json @@ -2,6 +2,7 @@ "config": { "abort": { "access_not_configured": "Please read the below message we got from Google:\n\n{message}", + "no_channel": "Please create a YouTube channel to be able to use the integration. Instructions can be found at {support_url}.", "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", "unknown": "[%key:common::config_flow::error::unknown%]" diff --git a/tests/components/youtube/fixtures/get_no_channel.json b/tests/components/youtube/fixtures/get_no_channel.json new file mode 100644 index 00000000000..7ec03c0461a --- /dev/null +++ b/tests/components/youtube/fixtures/get_no_channel.json @@ -0,0 +1,9 @@ +{ + "kind": "youtube#channelListResponse", + "etag": "8HTiiXpKCq-GJvDVOd88e5o_KGc", + "pageInfo": { + "totalResults": 0, + "resultsPerPage": 5 + }, + "items": [] +} diff --git a/tests/components/youtube/test_config_flow.py b/tests/components/youtube/test_config_flow.py index ed33947b593..5b91ff958f8 100644 --- a/tests/components/youtube/test_config_flow.py +++ b/tests/components/youtube/test_config_flow.py @@ -83,6 +83,46 @@ async def test_full_flow( assert result["options"] == {CONF_CHANNELS: ["UC_x5XG1OV2P6uZZ5FSM9Ttw"]} +async def test_flow_abort_without_channel( + hass: HomeAssistant, + hass_client_no_auth: ClientSessionGenerator, + current_request_with_host: None, +) -> None: + """Check abort flow if user has no channel.""" + result = await hass.config_entries.flow.async_init( + "youtube", context={"source": config_entries.SOURCE_USER} + ) + state = config_entry_oauth2_flow._encode_jwt( + hass, + { + "flow_id": result["flow_id"], + "redirect_uri": "https://example.com/auth/external/callback", + }, + ) + + assert result["url"] == ( + f"{GOOGLE_AUTH_URI}?response_type=code&client_id={CLIENT_ID}" + "&redirect_uri=https://example.com/auth/external/callback" + f"&state={state}&scope={'+'.join(SCOPES)}" + "&access_type=offline&prompt=consent" + ) + + client = await hass_client_no_auth() + resp = await client.get(f"/auth/external/callback?code=abcd&state={state}") + assert resp.status == 200 + assert resp.headers["content-type"] == "text/html; charset=utf-8" + + service = MockService(channel_fixture="youtube/get_no_channel.json") + with patch( + "homeassistant.components.youtube.async_setup_entry", return_value=True + ), patch("homeassistant.components.youtube.api.build", return_value=service), patch( + "homeassistant.components.youtube.config_flow.build", return_value=service + ): + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "no_channel" + + async def test_flow_http_error( hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator, From 4cb30e69ace562063c0e598ed24683bfc69622a0 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Sat, 10 Jun 2023 20:48:14 +0200 Subject: [PATCH 38/83] Update knx-frontend to 2023.6.9.195839 (#94404) --- homeassistant/components/knx/manifest.json | 2 +- homeassistant/components/knx/websocket.py | 12 ++++++++---- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index 61defa64e22..1f0a6d3cc5e 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -13,6 +13,6 @@ "requirements": [ "xknx==2.10.0", "xknxproject==3.1.1", - "knx_frontend==2023.5.31.141540" + "knx-frontend==2023.6.9.195839" ] } diff --git a/homeassistant/components/knx/websocket.py b/homeassistant/components/knx/websocket.py index d63ba89fbcc..a9da5036857 100644 --- a/homeassistant/components/knx/websocket.py +++ b/homeassistant/components/knx/websocket.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import TYPE_CHECKING, Final -from knx_frontend import get_build_id, locate_dir +from knx_frontend import entrypoint_js, is_dev_build, locate_dir import voluptuous as vol from xknx.telegram import TelegramDirection from xknxproject.exceptions import XknxProjectException @@ -31,9 +31,10 @@ async def register_panel(hass: HomeAssistant) -> None: if DOMAIN not in hass.data.get("frontend_panels", {}): path = locate_dir() - build_id = get_build_id() hass.http.register_static_path( - URL_BASE, path, cache_headers=(build_id != "dev") + URL_BASE, + path, + cache_headers=not is_dev_build, ) await panel_custom.async_register_panel( hass=hass, @@ -41,12 +42,13 @@ async def register_panel(hass: HomeAssistant) -> None: webcomponent_name="knx-frontend", sidebar_title=DOMAIN.upper(), sidebar_icon="mdi:bus-electric", - module_url=f"{URL_BASE}/entrypoint-{build_id}.js", + module_url=f"{URL_BASE}/{entrypoint_js()}", embed_iframe=True, require_admin=True, ) +@websocket_api.require_admin @websocket_api.websocket_command( { vol.Required("type"): "knx/info", @@ -129,6 +131,7 @@ async def ws_project_file_remove( connection.send_result(msg["id"]) +@websocket_api.require_admin @websocket_api.websocket_command( { vol.Required("type"): "knx/group_monitor_info", @@ -155,6 +158,7 @@ def ws_group_monitor_info( ) +@websocket_api.require_admin @websocket_api.websocket_command( { vol.Required("type"): "knx/subscribe_telegrams", diff --git a/requirements_all.txt b/requirements_all.txt index 2798c50cc4b..79fa1d989b7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1032,7 +1032,7 @@ kegtron-ble==0.4.0 kiwiki-client==0.1.1 # homeassistant.components.knx -knx_frontend==2023.5.31.141540 +knx-frontend==2023.6.9.195839 # homeassistant.components.konnected konnected==1.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7a3b9058373..05f638f3e32 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -791,7 +791,7 @@ justnimbus==0.6.0 kegtron-ble==0.4.0 # homeassistant.components.knx -knx_frontend==2023.5.31.141540 +knx-frontend==2023.6.9.195839 # homeassistant.components.konnected konnected==1.2.0 From 238eebb0b6dc14b9cc3705604e1f811d45509403 Mon Sep 17 00:00:00 2001 From: Christopher Bailey Date: Sun, 11 Jun 2023 10:22:12 -0400 Subject: [PATCH 39/83] Bump unifiprotect to 4.10.3 (#94416) * Bump unifiprotect to 4.10.3 * Reqs --- homeassistant/components/unifiprotect/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/unifiprotect/manifest.json b/homeassistant/components/unifiprotect/manifest.json index a414c03a0d4..cfa90664f36 100644 --- a/homeassistant/components/unifiprotect/manifest.json +++ b/homeassistant/components/unifiprotect/manifest.json @@ -41,7 +41,7 @@ "iot_class": "local_push", "loggers": ["pyunifiprotect", "unifi_discovery"], "quality_scale": "platinum", - "requirements": ["pyunifiprotect==4.10.2", "unifi-discovery==1.1.7"], + "requirements": ["pyunifiprotect==4.10.3", "unifi-discovery==1.1.7"], "ssdp": [ { "manufacturer": "Ubiquiti Networks", diff --git a/requirements_all.txt b/requirements_all.txt index 79fa1d989b7..22784bd8952 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2168,7 +2168,7 @@ pytrafikverket==0.3.3 pyudev==0.23.2 # homeassistant.components.unifiprotect -pyunifiprotect==4.10.2 +pyunifiprotect==4.10.3 # homeassistant.components.uptimerobot pyuptimerobot==22.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 05f638f3e32..33d1ed2ebf6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1579,7 +1579,7 @@ pytrafikverket==0.3.3 pyudev==0.23.2 # homeassistant.components.unifiprotect -pyunifiprotect==4.10.2 +pyunifiprotect==4.10.3 # homeassistant.components.uptimerobot pyuptimerobot==22.2.0 From 2505de35c9bb52a899bef343c055906fb71e3fea Mon Sep 17 00:00:00 2001 From: Sander Date: Mon, 12 Jun 2023 08:25:09 +0200 Subject: [PATCH 40/83] Fix: Xiaomi Miio Fan, delay off countdown unit conversion (#94428) --- homeassistant/components/xiaomi_miio/number.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/xiaomi_miio/number.py b/homeassistant/components/xiaomi_miio/number.py index 732710f7129..a8346caa894 100644 --- a/homeassistant/components/xiaomi_miio/number.py +++ b/homeassistant/components/xiaomi_miio/number.py @@ -441,7 +441,7 @@ class XiaomiNumberEntity(XiaomiCoordinatedMiioEntity, NumberEntity): return await self._try_command( "Setting delay off miio device failed.", self._device.delay_off, - delay_off_countdown * 60, + delay_off_countdown, ) async def async_set_led_brightness_level(self, level: int) -> bool: From 0083649e43fca29dc769af1d133a52c881398268 Mon Sep 17 00:00:00 2001 From: Yuxin Wang Date: Tue, 13 Jun 2023 10:38:56 -0400 Subject: [PATCH 41/83] Add unit inference for Amps and VA in APCUPSD integration (#94431) * Add unit inference for Amps and VA * Rename `init_integration` to `async_init_integration` for better consistency with HA naming style --- homeassistant/components/apcupsd/sensor.py | 2 ++ tests/components/apcupsd/__init__.py | 6 ++++-- tests/components/apcupsd/test_binary_sensor.py | 6 +++--- tests/components/apcupsd/test_init.py | 12 ++++++------ tests/components/apcupsd/test_sensor.py | 6 +++--- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/apcupsd/sensor.py b/homeassistant/components/apcupsd/sensor.py index 17168700f66..8b7034357df 100644 --- a/homeassistant/components/apcupsd/sensor.py +++ b/homeassistant/components/apcupsd/sensor.py @@ -430,7 +430,9 @@ INFERRED_UNITS = { " Percent": PERCENTAGE, " Volts": UnitOfElectricPotential.VOLT, " Ampere": UnitOfElectricCurrent.AMPERE, + " Amps": UnitOfElectricCurrent.AMPERE, " Volt-Ampere": UnitOfApparentPower.VOLT_AMPERE, + " VA": UnitOfApparentPower.VOLT_AMPERE, " Watts": UnitOfPower.WATT, " Hz": UnitOfFrequency.HERTZ, " C": UnitOfTemperature.CELSIUS, diff --git a/tests/components/apcupsd/__init__.py b/tests/components/apcupsd/__init__.py index f99b29c7bb7..f5c3f573030 100644 --- a/tests/components/apcupsd/__init__.py +++ b/tests/components/apcupsd/__init__.py @@ -26,6 +26,7 @@ MOCK_STATUS: Final = OrderedDict( ("LOADPCT", "14.0 Percent"), ("BCHARGE", "100.0 Percent"), ("TIMELEFT", "51.0 Minutes"), + ("NOMAPNT", "60.0 VA"), ("ITEMP", "34.6 C Internal"), ("MBATTCHG", "5 Percent"), ("MINTIMEL", "3 Minutes"), @@ -35,6 +36,7 @@ MOCK_STATUS: Final = OrderedDict( ("HITRANS", "139.0 Volts"), ("ALARMDEL", "30 Seconds"), ("BATTV", "13.7 Volts"), + ("OUTCURNT", "0.88 Amps"), ("LASTXFER", "Automatic or explicit self test"), ("NUMXFERS", "1"), ("XONBATT", "1970-01-01 00:00:00 0000"), @@ -74,7 +76,7 @@ MOCK_MINIMAL_STATUS: Final = OrderedDict( ) -async def init_integration( +async def async_init_integration( hass: HomeAssistant, host: str = "test", status=None ) -> MockConfigEntry: """Set up the APC UPS Daemon integration in HomeAssistant.""" @@ -95,7 +97,7 @@ async def init_integration( with patch("apcaccess.status.parse", return_value=status), patch( "apcaccess.status.get", return_value=b"" ): - await hass.config_entries.async_setup(entry.entry_id) + assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() return entry diff --git a/tests/components/apcupsd/test_binary_sensor.py b/tests/components/apcupsd/test_binary_sensor.py index c00707b7ff1..6ba9a09f837 100644 --- a/tests/components/apcupsd/test_binary_sensor.py +++ b/tests/components/apcupsd/test_binary_sensor.py @@ -2,12 +2,12 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er -from . import MOCK_STATUS, init_integration +from . import MOCK_STATUS, async_init_integration async def test_binary_sensor(hass: HomeAssistant) -> None: """Test states of binary sensor.""" - await init_integration(hass, status=MOCK_STATUS) + await async_init_integration(hass, status=MOCK_STATUS) registry = er.async_get(hass) state = hass.states.get("binary_sensor.ups_online_status") @@ -22,7 +22,7 @@ async def test_no_binary_sensor(hass: HomeAssistant) -> None: """Test binary sensor when STATFLAG is not available.""" status = MOCK_STATUS.copy() status.pop("STATFLAG") - await init_integration(hass, status=status) + await async_init_integration(hass, status=status) state = hass.states.get("binary_sensor.ups_online_status") assert state is None diff --git a/tests/components/apcupsd/test_init.py b/tests/components/apcupsd/test_init.py index eae5df9b0c1..6e00a382e79 100644 --- a/tests/components/apcupsd/test_init.py +++ b/tests/components/apcupsd/test_init.py @@ -9,7 +9,7 @@ from homeassistant.config_entries import SOURCE_USER, ConfigEntryState from homeassistant.const import STATE_UNAVAILABLE from homeassistant.core import HomeAssistant -from . import CONF_DATA, MOCK_MINIMAL_STATUS, MOCK_STATUS, init_integration +from . import CONF_DATA, MOCK_MINIMAL_STATUS, MOCK_STATUS, async_init_integration from tests.common import MockConfigEntry @@ -19,7 +19,7 @@ async def test_async_setup_entry(hass: HomeAssistant, status: OrderedDict) -> No """Test a successful setup entry.""" # Minimal status does not contain "SERIALNO" field, which is used to determine the # unique ID of this integration. But, the integration should work fine without it. - await init_integration(hass, status=status) + await async_init_integration(hass, status=status) # Verify successful setup by querying the status sensor. state = hass.states.get("binary_sensor.ups_online_status") @@ -34,8 +34,8 @@ async def test_multiple_integrations(hass: HomeAssistant) -> None: status1 = MOCK_STATUS | {"LOADPCT": "15.0 Percent", "SERIALNO": "XXXXX1"} status2 = MOCK_STATUS | {"LOADPCT": "16.0 Percent", "SERIALNO": "XXXXX2"} entries = ( - await init_integration(hass, host="test1", status=status1), - await init_integration(hass, host="test2", status=status2), + await async_init_integration(hass, host="test1", status=status1), + await async_init_integration(hass, host="test2", status=status2), ) assert len(hass.config_entries.async_entries(DOMAIN)) == 2 @@ -70,8 +70,8 @@ async def test_unload_remove(hass: HomeAssistant) -> None: """Test successful unload of entry.""" # Load two integrations from two mock hosts. entries = ( - await init_integration(hass, host="test1", status=MOCK_STATUS), - await init_integration(hass, host="test2", status=MOCK_MINIMAL_STATUS), + await async_init_integration(hass, host="test1", status=MOCK_STATUS), + await async_init_integration(hass, host="test2", status=MOCK_MINIMAL_STATUS), ) # Assert they are loaded. diff --git a/tests/components/apcupsd/test_sensor.py b/tests/components/apcupsd/test_sensor.py index a9f6820faa0..1b09e107682 100644 --- a/tests/components/apcupsd/test_sensor.py +++ b/tests/components/apcupsd/test_sensor.py @@ -16,12 +16,12 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er -from . import MOCK_STATUS, init_integration +from . import MOCK_STATUS, async_init_integration async def test_sensor(hass: HomeAssistant) -> None: """Test states of sensor.""" - await init_integration(hass, status=MOCK_STATUS) + await async_init_integration(hass, status=MOCK_STATUS) registry = er.async_get(hass) # Test a representative string sensor. @@ -89,7 +89,7 @@ async def test_sensor(hass: HomeAssistant) -> None: async def test_sensor_disabled(hass: HomeAssistant) -> None: """Test sensor disabled by default.""" - await init_integration(hass) + await async_init_integration(hass) registry = er.async_get(hass) # Test a representative integration-disabled sensor. From f5aa4f5866bea5c047ccb71a973e7ab41cd5ed00 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Mon, 12 Jun 2023 21:50:23 +0200 Subject: [PATCH 42/83] Fix manual update for Command Line (#94433) Manual update command line --- .../components/command_line/binary_sensor.py | 8 +++ .../components/command_line/cover.py | 9 ++- .../components/command_line/sensor.py | 8 +++ .../components/command_line/switch.py | 9 ++- .../command_line/test_binary_sensor.py | 59 ++++++++++++++++++- tests/components/command_line/test_cover.py | 58 ++++++++++++++++++ tests/components/command_line/test_sensor.py | 56 ++++++++++++++++++ tests/components/command_line/test_switch.py | 59 +++++++++++++++++++ 8 files changed, 263 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/command_line/binary_sensor.py b/homeassistant/components/command_line/binary_sensor.py index 8abe401ec9c..e7007b24592 100644 --- a/homeassistant/components/command_line/binary_sensor.py +++ b/homeassistant/components/command_line/binary_sensor.py @@ -30,6 +30,7 @@ from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.template import Template from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from homeassistant.util import dt as dt_util from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT, DOMAIN, LOGGER from .sensor import CommandSensorData @@ -183,3 +184,10 @@ class CommandBinarySensor(BinarySensorEntity): self._attr_is_on = False self.async_write_ha_state() + + async def async_update(self) -> None: + """Update the entity. + + Only used by the generic entity update service. + """ + await self._update_entity_state(dt_util.now()) diff --git a/homeassistant/components/command_line/cover.py b/homeassistant/components/command_line/cover.py index 2d2dc8c5fc2..041fa122f37 100644 --- a/homeassistant/components/command_line/cover.py +++ b/homeassistant/components/command_line/cover.py @@ -31,7 +31,7 @@ from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.template import Template from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from homeassistant.util import slugify +from homeassistant.util import dt as dt_util, slugify from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT, DOMAIN, LOGGER from .utils import call_shell_with_timeout, check_output_or_log @@ -220,6 +220,13 @@ class CommandCover(CoverEntity): self._state = int(payload) await self.async_update_ha_state(True) + async def async_update(self) -> None: + """Update the entity. + + Only used by the generic entity update service. + """ + await self._update_entity_state(dt_util.now()) + async def async_open_cover(self, **kwargs: Any) -> None: """Open the cover.""" await self.hass.async_add_executor_job(self._move_cover, self._command_open) diff --git a/homeassistant/components/command_line/sensor.py b/homeassistant/components/command_line/sensor.py index f42ac062081..8137173d613 100644 --- a/homeassistant/components/command_line/sensor.py +++ b/homeassistant/components/command_line/sensor.py @@ -33,6 +33,7 @@ from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.template import Template from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from homeassistant.util import dt as dt_util from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT, DOMAIN, LOGGER from .utils import check_output_or_log @@ -200,6 +201,13 @@ class CommandSensor(SensorEntity): self.async_write_ha_state() + async def async_update(self) -> None: + """Update the entity. + + Only used by the generic entity update service. + """ + await self._update_entity_state(dt_util.now()) + class CommandSensorData: """The class for handling the data retrieval.""" diff --git a/homeassistant/components/command_line/switch.py b/homeassistant/components/command_line/switch.py index 1a3dd39a342..4a33d8072d7 100644 --- a/homeassistant/components/command_line/switch.py +++ b/homeassistant/components/command_line/switch.py @@ -34,7 +34,7 @@ from homeassistant.helpers.issue_registry import IssueSeverity, async_create_iss from homeassistant.helpers.template import Template from homeassistant.helpers.template_entity import ManualTriggerEntity from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from homeassistant.util import slugify +from homeassistant.util import dt as dt_util, slugify from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT, DOMAIN, LOGGER from .utils import call_shell_with_timeout, check_output_or_log @@ -240,6 +240,13 @@ class CommandSwitch(ManualTriggerEntity, SwitchEntity): self._process_manual_data(payload) await self.async_update_ha_state(True) + async def async_update(self) -> None: + """Update the entity. + + Only used by the generic entity update service. + """ + await self._update_entity_state(dt_util.now()) + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" if await self._switch(self._command_on) and not self._command_state: diff --git a/tests/components/command_line/test_binary_sensor.py b/tests/components/command_line/test_binary_sensor.py index eb6b52a66be..9e97f053e07 100644 --- a/tests/components/command_line/test_binary_sensor.py +++ b/tests/components/command_line/test_binary_sensor.py @@ -12,7 +12,11 @@ from homeassistant import setup from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.command_line.binary_sensor import CommandBinarySensor from homeassistant.components.command_line.const import DOMAIN -from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.components.homeassistant import ( + DOMAIN as HA_DOMAIN, + SERVICE_UPDATE_ENTITY, +) +from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er import homeassistant.helpers.issue_registry as ir @@ -252,3 +256,56 @@ async def test_updating_to_often( ) await asyncio.sleep(0.2) + + +async def test_updating_manually( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test handling manual updating using homeassistant udate_entity service.""" + await setup.async_setup_component(hass, HA_DOMAIN, {}) + called = [] + + class MockCommandBinarySensor(CommandBinarySensor): + """Mock entity that updates slow.""" + + async def _async_update(self) -> None: + """Update slow.""" + called.append(1) + # Add waiting time + await asyncio.sleep(1) + + with patch( + "homeassistant.components.command_line.binary_sensor.CommandBinarySensor", + side_effect=MockCommandBinarySensor, + ): + await setup.async_setup_component( + hass, + DOMAIN, + { + "command_line": [ + { + "binary_sensor": { + "name": "Test", + "command": "echo 1", + "payload_on": "1", + "payload_off": "0", + "scan_interval": 10, + } + } + ] + }, + ) + await hass.async_block_till_done() + + assert len(called) == 1 + + await hass.services.async_call( + HA_DOMAIN, + SERVICE_UPDATE_ENTITY, + {ATTR_ENTITY_ID: ["binary_sensor.test"]}, + blocking=True, + ) + await hass.async_block_till_done() + assert len(called) == 2 + + await asyncio.sleep(0.2) diff --git a/tests/components/command_line/test_cover.py b/tests/components/command_line/test_cover.py index d977c202b04..bb41ed16e82 100644 --- a/tests/components/command_line/test_cover.py +++ b/tests/components/command_line/test_cover.py @@ -13,6 +13,10 @@ from homeassistant import config as hass_config, setup from homeassistant.components.command_line import DOMAIN from homeassistant.components.command_line.cover import CommandCover from homeassistant.components.cover import DOMAIN as COVER_DOMAIN, SCAN_INTERVAL +from homeassistant.components.homeassistant import ( + DOMAIN as HA_DOMAIN, + SERVICE_UPDATE_ENTITY, +) from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_CLOSE_COVER, @@ -378,3 +382,57 @@ async def test_updating_to_often( ) await asyncio.sleep(0.2) + + +async def test_updating_manually( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test handling manual updating using homeassistant udate_entity service.""" + await setup.async_setup_component(hass, HA_DOMAIN, {}) + called = [] + + class MockCommandCover(CommandCover): + """Mock entity that updates slow.""" + + async def _async_update(self) -> None: + """Update slow.""" + called.append(1) + # Add waiting time + await asyncio.sleep(1) + + with patch( + "homeassistant.components.command_line.cover.CommandCover", + side_effect=MockCommandCover, + ): + await setup.async_setup_component( + hass, + DOMAIN, + { + "command_line": [ + { + "cover": { + "command_state": "echo 1", + "value_template": "{{ value }}", + "name": "Test", + "scan_interval": 10, + } + } + ] + }, + ) + await hass.async_block_till_done() + + async_fire_time_changed(hass, dt_util.now() + timedelta(seconds=10)) + await hass.async_block_till_done() + assert len(called) == 1 + + await hass.services.async_call( + HA_DOMAIN, + SERVICE_UPDATE_ENTITY, + {ATTR_ENTITY_ID: ["cover.test"]}, + blocking=True, + ) + await hass.async_block_till_done() + assert len(called) == 2 + + await asyncio.sleep(0.2) diff --git a/tests/components/command_line/test_sensor.py b/tests/components/command_line/test_sensor.py index 87360d0e251..fd3ff8613ec 100644 --- a/tests/components/command_line/test_sensor.py +++ b/tests/components/command_line/test_sensor.py @@ -11,7 +11,12 @@ import pytest from homeassistant import setup from homeassistant.components.command_line import DOMAIN from homeassistant.components.command_line.sensor import CommandSensor +from homeassistant.components.homeassistant import ( + DOMAIN as HA_DOMAIN, + SERVICE_UPDATE_ENTITY, +) from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er import homeassistant.helpers.issue_registry as ir @@ -586,3 +591,54 @@ async def test_updating_to_often( ) await asyncio.sleep(0.2) + + +async def test_updating_manually( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test handling manual updating using homeassistant udate_entity service.""" + await setup.async_setup_component(hass, HA_DOMAIN, {}) + called = [] + + class MockCommandSensor(CommandSensor): + """Mock entity that updates slow.""" + + async def _async_update(self) -> None: + """Update slow.""" + called.append(1) + # Add waiting time + await asyncio.sleep(1) + + with patch( + "homeassistant.components.command_line.sensor.CommandSensor", + side_effect=MockCommandSensor, + ): + await setup.async_setup_component( + hass, + DOMAIN, + { + "command_line": [ + { + "sensor": { + "name": "Test", + "command": "echo 1", + "scan_interval": 10, + } + } + ] + }, + ) + await hass.async_block_till_done() + + assert len(called) == 1 + + await hass.services.async_call( + HA_DOMAIN, + SERVICE_UPDATE_ENTITY, + {ATTR_ENTITY_ID: ["sensor.test"]}, + blocking=True, + ) + await hass.async_block_till_done() + assert len(called) == 2 + + await asyncio.sleep(0.2) diff --git a/tests/components/command_line/test_switch.py b/tests/components/command_line/test_switch.py index 88a87588375..e5331fbe7dd 100644 --- a/tests/components/command_line/test_switch.py +++ b/tests/components/command_line/test_switch.py @@ -14,6 +14,10 @@ import pytest from homeassistant import setup from homeassistant.components.command_line import DOMAIN from homeassistant.components.command_line.switch import CommandSwitch +from homeassistant.components.homeassistant import ( + DOMAIN as HA_DOMAIN, + SERVICE_UPDATE_ENTITY, +) from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN, SCAN_INTERVAL from homeassistant.const import ( ATTR_ENTITY_ID, @@ -696,3 +700,58 @@ async def test_updating_to_often( ) await asyncio.sleep(0.2) + + +async def test_updating_manually( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test handling manual updating using homeassistant udate_entity service.""" + await setup.async_setup_component(hass, HA_DOMAIN, {}) + called = [] + + class MockCommandSwitch(CommandSwitch): + """Mock entity that updates slow.""" + + async def _async_update(self) -> None: + """Update slow.""" + called.append(1) + # Add waiting time + await asyncio.sleep(1) + + with patch( + "homeassistant.components.command_line.switch.CommandSwitch", + side_effect=MockCommandSwitch, + ): + await setup.async_setup_component( + hass, + DOMAIN, + { + "command_line": [ + { + "switch": { + "command_state": "echo 1", + "command_on": "echo 2", + "command_off": "echo 3", + "name": "Test", + "scan_interval": 10, + } + } + ] + }, + ) + await hass.async_block_till_done() + + async_fire_time_changed(hass, dt_util.now() + timedelta(seconds=10)) + await hass.async_block_till_done() + assert len(called) == 1 + + await hass.services.async_call( + HA_DOMAIN, + SERVICE_UPDATE_ENTITY, + {ATTR_ENTITY_ID: ["switch.test"]}, + blocking=True, + ) + await hass.async_block_till_done() + assert len(called) == 2 + + await asyncio.sleep(0.2) From 25a4679266d17e8f6164f9b36b7c0174e8c9a56b Mon Sep 17 00:00:00 2001 From: G Johansson Date: Mon, 12 Jun 2023 21:51:57 +0200 Subject: [PATCH 43/83] Fix reload service in Command Line (#94436) * Fix reload in Command Line * Add read new yaml --- .../components/command_line/__init__.py | 41 ++++++++++--- .../command_line/fixtures/configuration.yaml | 13 ++-- .../fixtures/configuration_empty.yaml | 0 tests/components/command_line/test_cover.py | 44 +------------ tests/components/command_line/test_init.py | 61 ++++++++++++++++++- 5 files changed, 101 insertions(+), 58 deletions(-) create mode 100644 tests/components/command_line/fixtures/configuration_empty.yaml diff --git a/homeassistant/components/command_line/__init__.py b/homeassistant/components/command_line/__init__.py index 906e28052da..6f536bf4744 100644 --- a/homeassistant/components/command_line/__init__.py +++ b/homeassistant/components/command_line/__init__.py @@ -46,12 +46,15 @@ from homeassistant.const import ( CONF_UNIQUE_ID, CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, + SERVICE_RELOAD, Platform, ) -from homeassistant.core import HomeAssistant +from homeassistant.core import Event, HomeAssistant, ServiceCall from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.reload import async_setup_reload_service +from homeassistant.helpers.entity_platform import async_get_platforms +from homeassistant.helpers.reload import async_integration_yaml_config +from homeassistant.helpers.service import async_register_admin_service from homeassistant.helpers.typing import ConfigType from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT, DOMAIN @@ -163,14 +166,39 @@ CONFIG_SCHEMA = vol.Schema( async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up Command Line from yaml config.""" - command_line_config: list[dict[str, dict[str, Any]]] = config.get(DOMAIN, []) + + async def _reload_config(call: Event | ServiceCall) -> None: + """Reload Command Line.""" + reload_config = await async_integration_yaml_config(hass, "command_line") + reset_platforms = async_get_platforms(hass, "command_line") + for reset_platform in reset_platforms: + _LOGGER.debug("Reload resetting platform: %s", reset_platform.domain) + await reset_platform.async_reset() + if not reload_config: + return + await async_load_platforms(hass, reload_config.get(DOMAIN, []), reload_config) + + async_register_admin_service(hass, DOMAIN, SERVICE_RELOAD, _reload_config) + + await async_load_platforms(hass, config.get(DOMAIN, []), config) + + return True + + +async def async_load_platforms( + hass: HomeAssistant, + command_line_config: list[dict[str, dict[str, Any]]], + config: ConfigType, +) -> None: + """Load platforms from yaml.""" if not command_line_config: - return True + return _LOGGER.debug("Full config loaded: %s", command_line_config) load_coroutines: list[Coroutine[Any, Any, None]] = [] platforms: list[Platform] = [] + reload_configs: list[tuple] = [] for platform_config in command_line_config: for platform, _config in platform_config.items(): if (mapped_platform := PLATFORM_MAPPING[platform]) not in platforms: @@ -180,6 +208,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: platform_config, PLATFORM_MAPPING[platform], ) + reload_configs.append((PLATFORM_MAPPING[platform], _config)) load_coroutines.append( discovery.async_load_platform( hass, @@ -190,10 +219,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ) ) - await async_setup_reload_service(hass, DOMAIN, platforms) - if load_coroutines: _LOGGER.debug("Loading platforms: %s", platforms) await asyncio.gather(*load_coroutines) - - return True diff --git a/tests/components/command_line/fixtures/configuration.yaml b/tests/components/command_line/fixtures/configuration.yaml index f210b640338..43e6f641966 100644 --- a/tests/components/command_line/fixtures/configuration.yaml +++ b/tests/components/command_line/fixtures/configuration.yaml @@ -1,6 +1,7 @@ -cover: - - platform: command_line - covers: - from_yaml: - command_state: "echo closed" - value_template: "{{ value }}" +command_line: + - "binary_sensor": + "name": "Test" + "command": "echo 1" + "payload_on": "1" + "payload_off": "0" + "command_timeout": 15 diff --git a/tests/components/command_line/fixtures/configuration_empty.yaml b/tests/components/command_line/fixtures/configuration_empty.yaml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/components/command_line/test_cover.py b/tests/components/command_line/test_cover.py index bb41ed16e82..cbbe2e78eac 100644 --- a/tests/components/command_line/test_cover.py +++ b/tests/components/command_line/test_cover.py @@ -9,7 +9,7 @@ from unittest.mock import patch import pytest -from homeassistant import config as hass_config, setup +from homeassistant import setup from homeassistant.components.command_line import DOMAIN from homeassistant.components.command_line.cover import CommandCover from homeassistant.components.cover import DOMAIN as COVER_DOMAIN, SCAN_INTERVAL @@ -21,7 +21,6 @@ from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_CLOSE_COVER, SERVICE_OPEN_COVER, - SERVICE_RELOAD, SERVICE_STOP_COVER, ) from homeassistant.core import HomeAssistant @@ -29,7 +28,7 @@ from homeassistant.helpers import entity_registry as er import homeassistant.helpers.issue_registry as ir import homeassistant.util.dt as dt_util -from tests.common import async_fire_time_changed, get_fixture_path +from tests.common import async_fire_time_changed async def test_no_covers_platform_yaml( @@ -214,45 +213,6 @@ async def test_state_value(hass: HomeAssistant) -> None: assert entity_state.state == "closed" -@pytest.mark.parametrize( - "get_config", - [ - { - "command_line": [ - { - "cover": { - "command_state": "echo open", - "value_template": "{{ value }}", - "name": "Test", - } - } - ] - } - ], -) -async def test_reload(hass: HomeAssistant, load_yaml_integration: None) -> None: - """Verify we can reload command_line covers.""" - - entity_state = hass.states.get("cover.test") - assert entity_state - assert entity_state.state == "unknown" - - yaml_path = get_fixture_path("configuration.yaml", "command_line") - with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path): - await hass.services.async_call( - "command_line", - SERVICE_RELOAD, - {}, - blocking=True, - ) - await hass.async_block_till_done() - - assert len(hass.states.async_all()) == 1 - - assert not hass.states.get("cover.test") - assert hass.states.get("cover.from_yaml") - - @pytest.mark.parametrize( "get_config", [ diff --git a/tests/components/command_line/test_init.py b/tests/components/command_line/test_init.py index 06d7b8c41dc..53f985961f3 100644 --- a/tests/components/command_line/test_init.py +++ b/tests/components/command_line/test_init.py @@ -2,12 +2,17 @@ from __future__ import annotations from datetime import timedelta +from unittest.mock import patch -from homeassistant.const import STATE_ON, STATE_OPEN +import pytest + +from homeassistant import config as hass_config +from homeassistant.components.command_line.const import DOMAIN +from homeassistant.const import SERVICE_RELOAD, STATE_ON, STATE_OPEN from homeassistant.core import HomeAssistant import homeassistant.util.dt as dt_util -from tests.common import async_fire_time_changed +from tests.common import async_fire_time_changed, get_fixture_path async def test_setup_config(hass: HomeAssistant, load_yaml_integration: None) -> None: @@ -25,3 +30,55 @@ async def test_setup_config(hass: HomeAssistant, load_yaml_integration: None) -> assert state_sensor.state == "5" assert state_cover.state == STATE_OPEN assert state_switch.state == STATE_ON + + +async def test_reload_service( + hass: HomeAssistant, load_yaml_integration: None, caplog: pytest.LogCaptureFixture +) -> None: + """Test reload serviice.""" + + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=10)) + await hass.async_block_till_done() + + state_binary_sensor = hass.states.get("binary_sensor.test") + state_sensor = hass.states.get("sensor.test") + assert state_binary_sensor.state == STATE_ON + assert state_sensor.state == "5" + + caplog.clear() + + yaml_path = get_fixture_path("configuration.yaml", "command_line") + with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path): + await hass.services.async_call( + DOMAIN, + SERVICE_RELOAD, + {}, + blocking=True, + ) + await hass.async_block_till_done() + + assert "Loading config" in caplog.text + + state_binary_sensor = hass.states.get("binary_sensor.test") + state_sensor = hass.states.get("sensor.test") + assert state_binary_sensor.state == STATE_ON + assert not state_sensor + + caplog.clear() + + yaml_path = get_fixture_path("configuration_empty.yaml", "command_line") + with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path): + await hass.services.async_call( + DOMAIN, + SERVICE_RELOAD, + {}, + blocking=True, + ) + await hass.async_block_till_done() + + state_binary_sensor = hass.states.get("binary_sensor.test") + state_sensor = hass.states.get("sensor.test") + assert not state_binary_sensor + assert not state_sensor + + assert "Loading config" not in caplog.text From fa8e9523242cdc4c4680e66331a6ba799bb03aef Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 12 Jun 2023 21:34:09 -0400 Subject: [PATCH 44/83] Set default value for endpoint in zwave device automations (#94445) * Set default value for endpoint in zwave device automations * add test case --- .../components/zwave_js/device_action.py | 2 +- .../components/zwave_js/device_trigger.py | 2 +- .../components/zwave_js/test_device_action.py | 27 +++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zwave_js/device_action.py b/homeassistant/components/zwave_js/device_action.py index 20c37b5cbb6..18a3ccef7d8 100644 --- a/homeassistant/components/zwave_js/device_action.py +++ b/homeassistant/components/zwave_js/device_action.py @@ -101,7 +101,7 @@ RESET_METER_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend( SET_CONFIG_PARAMETER_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend( { vol.Required(CONF_TYPE): SERVICE_SET_CONFIG_PARAMETER, - vol.Required(ATTR_ENDPOINT): vol.Coerce(int), + vol.Required(ATTR_ENDPOINT, default=0): vol.Coerce(int), vol.Required(ATTR_CONFIG_PARAMETER): vol.Any(int, str), vol.Required(ATTR_CONFIG_PARAMETER_BITMASK): vol.Any(None, int, str), vol.Required(ATTR_VALUE): vol.Coerce(int), diff --git a/homeassistant/components/zwave_js/device_trigger.py b/homeassistant/components/zwave_js/device_trigger.py index a0ac70ccb31..da26e4f293e 100644 --- a/homeassistant/components/zwave_js/device_trigger.py +++ b/homeassistant/components/zwave_js/device_trigger.py @@ -161,7 +161,7 @@ BASE_VALUE_UPDATED_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend( vol.Required(ATTR_COMMAND_CLASS): vol.In([cc.value for cc in CommandClass]), vol.Required(ATTR_PROPERTY): vol.Any(int, str), vol.Optional(ATTR_PROPERTY_KEY): vol.Any(None, vol.Coerce(int), str), - vol.Optional(ATTR_ENDPOINT): vol.Any(None, vol.Coerce(int)), + vol.Optional(ATTR_ENDPOINT, default=0): vol.Any(None, vol.Coerce(int)), vol.Optional(ATTR_FROM): VALUE_SCHEMA, vol.Optional(ATTR_TO): VALUE_SCHEMA, } diff --git a/tests/components/zwave_js/test_device_action.py b/tests/components/zwave_js/test_device_action.py index 97631c94501..ccb65c1d8fa 100644 --- a/tests/components/zwave_js/test_device_action.py +++ b/tests/components/zwave_js/test_device_action.py @@ -196,6 +196,21 @@ async def test_actions( "value": 1, }, }, + { + "trigger": { + "platform": "event", + "event_type": "test_event_set_config_parameter_no_endpoint", + }, + "action": { + "domain": DOMAIN, + "type": "set_config_parameter", + "device_id": device.id, + "parameter": 1, + "bitmask": None, + "subtype": "3 (Beeper)", + "value": 1, + }, + }, ] }, ) @@ -245,6 +260,18 @@ async def test_actions( assert args[1] == 1 assert args[2] == 1 + with patch( + "homeassistant.components.zwave_js.services.async_set_config_parameter" + ) as mock_call: + hass.bus.async_fire("test_event_set_config_parameter_no_endpoint") + await hass.async_block_till_done() + mock_call.assert_called_once() + args = mock_call.call_args_list[0][0] + assert len(args) == 3 + assert args[0].node_id == 13 + assert args[1] == 1 + assert args[2] == 1 + async def test_actions_multiple_calls( hass: HomeAssistant, From e83f0bb7a5a82981e7e93884b34a90fa40a764cd Mon Sep 17 00:00:00 2001 From: mover85 Date: Tue, 13 Jun 2023 06:58:20 +1200 Subject: [PATCH 45/83] Revert "Bump pydaikin 2.9.1 (#93635)" (#94469) Revert to pydaikin 2.9.0 --- homeassistant/components/daikin/manifest.json | 2 +- homeassistant/components/daikin/switch.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/daikin/manifest.json b/homeassistant/components/daikin/manifest.json index 02a8cdbe68f..6f90b0cf5ef 100644 --- a/homeassistant/components/daikin/manifest.json +++ b/homeassistant/components/daikin/manifest.json @@ -7,6 +7,6 @@ "iot_class": "local_polling", "loggers": ["pydaikin"], "quality_scale": "platinum", - "requirements": ["pydaikin==2.9.1"], + "requirements": ["pydaikin==2.9.0"], "zeroconf": ["_dkapi._tcp.local."] } diff --git a/homeassistant/components/daikin/switch.py b/homeassistant/components/daikin/switch.py index 1b83f7f7330..37b3ec45c4c 100644 --- a/homeassistant/components/daikin/switch.py +++ b/homeassistant/components/daikin/switch.py @@ -42,7 +42,7 @@ async def async_setup_entry( [ DaikinZoneSwitch(daikin_api, zone_id) for zone_id, zone in enumerate(zones) - if zone[0] != ("-", "0") + if zone != ("-", "0") ] ) if daikin_api.device.support_advanced_modes: diff --git a/requirements_all.txt b/requirements_all.txt index 22784bd8952..f1a98a39434 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1576,7 +1576,7 @@ pycsspeechtts==1.0.8 # pycups==1.9.73 # homeassistant.components.daikin -pydaikin==2.9.1 +pydaikin==2.9.0 # homeassistant.components.danfoss_air pydanfossair==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 33d1ed2ebf6..d93a1dd8d39 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1164,7 +1164,7 @@ pycoolmasternet-async==0.1.5 pycsspeechtts==1.0.8 # homeassistant.components.daikin -pydaikin==2.9.1 +pydaikin==2.9.0 # homeassistant.components.deconz pydeconz==112 From 576cf525733e5009036efe21a03e3b997fcef459 Mon Sep 17 00:00:00 2001 From: Chris Phillips Date: Tue, 13 Jun 2023 12:37:30 -0700 Subject: [PATCH 46/83] Bump russound_rio to 1.0.0 (#94500) --- homeassistant/components/russound_rio/manifest.json | 2 +- requirements_all.txt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/russound_rio/manifest.json b/homeassistant/components/russound_rio/manifest.json index 728c40121e0..4f35bf69736 100644 --- a/homeassistant/components/russound_rio/manifest.json +++ b/homeassistant/components/russound_rio/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/russound_rio", "iot_class": "local_push", "loggers": ["russound_rio"], - "requirements": ["russound_rio==0.1.8"] + "requirements": ["russound-rio==1.0.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index f1a98a39434..df228272354 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2293,12 +2293,12 @@ rpi-bad-power==0.1.0 # homeassistant.components.rtsp_to_webrtc rtsp-to-webrtc==0.5.1 +# homeassistant.components.russound_rio +russound-rio==1.0.0 + # homeassistant.components.russound_rnet russound==0.1.9 -# homeassistant.components.russound_rio -russound_rio==0.1.8 - # homeassistant.components.ruuvitag_ble ruuvitag-ble==0.1.1 From fd0404bb4afced8807e20809de06fb182d6f358b Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 13 Jun 2023 04:52:55 -0400 Subject: [PATCH 47/83] Fix entity and device selector TypedDict's (#94510) --- homeassistant/helpers/selector.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index 2e7df07cf04..afd38bf7636 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -189,8 +189,6 @@ class DeviceFilterSelectorConfig(TypedDict, total=False): integration: str manufacturer: str model: str - entity: EntityFilterSelectorConfig | list[EntityFilterSelectorConfig] - filter: DeviceFilterSelectorConfig | list[DeviceFilterSelectorConfig] class ActionSelectorConfig(TypedDict): @@ -546,14 +544,12 @@ class DateTimeSelector(Selector[DateTimeSelectorConfig]): return data -class DeviceSelectorConfig(TypedDict, total=False): +class DeviceSelectorConfig(DeviceFilterSelectorConfig, total=False): """Class to represent a device selector config.""" - integration: str - manufacturer: str - model: str entity: EntityFilterSelectorConfig | list[EntityFilterSelectorConfig] multiple: bool + filter: DeviceFilterSelectorConfig | list[DeviceFilterSelectorConfig] @SELECTORS.register("device") @@ -622,6 +618,7 @@ class EntitySelectorConfig(EntityFilterSelectorConfig, total=False): exclude_entities: list[str] include_entities: list[str] multiple: bool + filter: EntityFilterSelectorConfig | list[EntityFilterSelectorConfig] @SELECTORS.register("entity") From d557c6e43ec78725807a8bc587d37249c5bce693 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 13 Jun 2023 06:32:43 -1000 Subject: [PATCH 48/83] Bump yalexs-ble to 2.1.18 (#94547) --- homeassistant/components/august/manifest.json | 2 +- homeassistant/components/yalexs_ble/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index eeaa5f6c622..ca4e799f16b 100644 --- a/homeassistant/components/august/manifest.json +++ b/homeassistant/components/august/manifest.json @@ -28,5 +28,5 @@ "documentation": "https://www.home-assistant.io/integrations/august", "iot_class": "cloud_push", "loggers": ["pubnub", "yalexs"], - "requirements": ["yalexs==1.5.1", "yalexs-ble==2.1.17"] + "requirements": ["yalexs==1.5.1", "yalexs-ble==2.1.18"] } diff --git a/homeassistant/components/yalexs_ble/manifest.json b/homeassistant/components/yalexs_ble/manifest.json index 8aa795b970e..4822b2d2704 100644 --- a/homeassistant/components/yalexs_ble/manifest.json +++ b/homeassistant/components/yalexs_ble/manifest.json @@ -12,5 +12,5 @@ "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/yalexs_ble", "iot_class": "local_push", - "requirements": ["yalexs-ble==2.1.17"] + "requirements": ["yalexs-ble==2.1.18"] } diff --git a/requirements_all.txt b/requirements_all.txt index df228272354..334dc1be577 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2698,7 +2698,7 @@ yalesmartalarmclient==0.3.9 # homeassistant.components.august # homeassistant.components.yalexs_ble -yalexs-ble==2.1.17 +yalexs-ble==2.1.18 # homeassistant.components.august yalexs==1.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d93a1dd8d39..53c8a67afc2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1968,7 +1968,7 @@ yalesmartalarmclient==0.3.9 # homeassistant.components.august # homeassistant.components.yalexs_ble -yalexs-ble==2.1.17 +yalexs-ble==2.1.18 # homeassistant.components.august yalexs==1.5.1 From a63ce8100e7d039a709aa586166d29d2d162ad12 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Tue, 13 Jun 2023 13:37:56 -0600 Subject: [PATCH 49/83] Bump `regenmaschine` to 2023.06.0 (#94554) --- homeassistant/components/rainmachine/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/rainmachine/manifest.json b/homeassistant/components/rainmachine/manifest.json index 574ca3d7f43..dabae5ff8c6 100644 --- a/homeassistant/components/rainmachine/manifest.json +++ b/homeassistant/components/rainmachine/manifest.json @@ -10,7 +10,7 @@ "integration_type": "device", "iot_class": "local_polling", "loggers": ["regenmaschine"], - "requirements": ["regenmaschine==2023.05.1"], + "requirements": ["regenmaschine==2023.06.0"], "zeroconf": [ { "type": "_http._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index 334dc1be577..4f0e1957afa 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2246,7 +2246,7 @@ rapt-ble==0.1.1 raspyrfm-client==1.2.8 # homeassistant.components.rainmachine -regenmaschine==2023.05.1 +regenmaschine==2023.06.0 # homeassistant.components.renault renault-api==0.1.13 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 53c8a67afc2..bb5fa31f9fa 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1630,7 +1630,7 @@ radiotherm==2.1.0 rapt-ble==0.1.1 # homeassistant.components.rainmachine -regenmaschine==2023.05.1 +regenmaschine==2023.06.0 # homeassistant.components.renault renault-api==0.1.13 From 70d33129d4550f65a4c52aa20105f06144b8eebc Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 13 Jun 2023 22:27:14 +0200 Subject: [PATCH 50/83] Update Home Assistant base image to 2023.06.0 (#94556) --- build.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build.yaml b/build.yaml index 11b60a66295..b32aa38dff6 100644 --- a/build.yaml +++ b/build.yaml @@ -1,11 +1,11 @@ image: homeassistant/{arch}-homeassistant shadow_repository: ghcr.io/home-assistant build_from: - aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2023.05.0 - armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2023.05.0 - armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2023.05.0 - amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2023.05.0 - i386: ghcr.io/home-assistant/i386-homeassistant-base:2023.05.0 + aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2023.06.0 + armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2023.06.0 + armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2023.06.0 + amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2023.06.0 + i386: ghcr.io/home-assistant/i386-homeassistant-base:2023.06.0 codenotary: signer: notary@home-assistant.io base_image: notary@home-assistant.io From f67577ebe077b293bbd15ab790aa868053195610 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Wed, 14 Jun 2023 21:00:21 +0000 Subject: [PATCH 51/83] Catch InvalidAuthError in `shutdown()` method for Shelly gen2 devices (#94563) * Catch InvalidAuthError in shutdown() method * Add test * Revert unwanted change in tests --- .../components/shelly/coordinator.py | 5 +- tests/components/shelly/test_coordinator.py | 55 ++++++++++++++++++- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/shelly/coordinator.py b/homeassistant/components/shelly/coordinator.py index 85207ee4475..6d7b3496880 100644 --- a/homeassistant/components/shelly/coordinator.py +++ b/homeassistant/components/shelly/coordinator.py @@ -535,7 +535,10 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]): async def shutdown(self) -> None: """Shutdown the coordinator.""" if self.device.connected: - await async_stop_scanner(self.device) + try: + await async_stop_scanner(self.device) + except InvalidAuthError: + self.entry.async_start_reauth(self.hass) await self.device.shutdown() await self._async_disconnected() diff --git a/tests/components/shelly/test_coordinator.py b/tests/components/shelly/test_coordinator.py index 2f267a208ca..9039893999d 100644 --- a/tests/components/shelly/test_coordinator.py +++ b/tests/components/shelly/test_coordinator.py @@ -1,6 +1,6 @@ """Tests for Shelly coordinator.""" from datetime import timedelta -from unittest.mock import AsyncMock +from unittest.mock import AsyncMock, patch from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError @@ -335,6 +335,59 @@ async def test_rpc_reload_on_cfg_change( assert hass.states.get("switch.test_switch_0") is None +async def test_rpc_reload_with_invalid_auth( + hass: HomeAssistant, mock_rpc_device, monkeypatch +) -> None: + """Test RPC when InvalidAuthError is raising during config entry reload.""" + with patch( + "homeassistant.components.shelly.coordinator.async_stop_scanner", + side_effect=[None, InvalidAuthError, None], + ): + entry = await init_integration(hass, 2) + + inject_rpc_device_event( + monkeypatch, + mock_rpc_device, + { + "events": [ + { + "data": [], + "event": "config_changed", + "id": 1, + "ts": 1668522399.2, + }, + { + "data": [], + "id": 2, + "ts": 1668522399.2, + }, + ], + "ts": 1668522399.2, + }, + ) + + await hass.async_block_till_done() + + # Move time to generate reconnect + async_fire_time_changed( + hass, dt_util.utcnow() + timedelta(seconds=RPC_RECONNECT_INTERVAL) + ) + await hass.async_block_till_done() + + assert entry.state == ConfigEntryState.LOADED + + flows = hass.config_entries.flow.async_progress() + assert len(flows) == 1 + + flow = flows[0] + assert flow.get("step_id") == "reauth_confirm" + assert flow.get("handler") == DOMAIN + + assert "context" in flow + assert flow["context"].get("source") == SOURCE_REAUTH + assert flow["context"].get("entry_id") == entry.entry_id + + async def test_rpc_click_event( hass: HomeAssistant, mock_rpc_device, events, monkeypatch ) -> None: From d28d9091140701a8d051b9c4c5f35244339d7ff1 Mon Sep 17 00:00:00 2001 From: Ian Foster Date: Wed, 14 Jun 2023 13:06:55 -0700 Subject: [PATCH 52/83] Fix keyboard_remote for python 3.11 (#94570) * started work to update keyboard_remote to work with python 3.11 * updated function names * all checks pass * fixed asyncio for python 3.11 * cleanup * Update homeassistant/components/keyboard_remote/__init__.py Co-authored-by: Martin Hjelmare * Update __init__.py added: from __future__ import annotations * Fix typing --------- Co-authored-by: Martin Hjelmare --- .../components/keyboard_remote/__init__.py | 81 +++++++++++-------- .../components/keyboard_remote/manifest.json | 2 +- requirements_all.txt | 8 +- 3 files changed, 53 insertions(+), 38 deletions(-) diff --git a/homeassistant/components/keyboard_remote/__init__.py b/homeassistant/components/keyboard_remote/__init__.py index f0f1497f940..e3d280c2944 100644 --- a/homeassistant/components/keyboard_remote/__init__.py +++ b/homeassistant/components/keyboard_remote/__init__.py @@ -1,11 +1,14 @@ """Receive signals from a keyboard and use it as a remote control.""" # pylint: disable=import-error +from __future__ import annotations + import asyncio from contextlib import suppress import logging import os +from typing import Any -import aionotify +from asyncinotify import Inotify, Mask from evdev import InputDevice, categorize, ecodes, list_devices import voluptuous as vol @@ -64,9 +67,9 @@ CONFIG_SCHEMA = vol.Schema( async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the keyboard_remote.""" - config = config[DOMAIN] + domain_config: list[dict[str, Any]] = config[DOMAIN] - remote = KeyboardRemote(hass, config) + remote = KeyboardRemote(hass, domain_config) remote.setup() return True @@ -75,12 +78,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: class KeyboardRemote: """Manage device connection/disconnection using inotify to asynchronously monitor.""" - def __init__(self, hass, config): + def __init__(self, hass: HomeAssistant, config: list[dict[str, Any]]) -> None: """Create handlers and setup dictionaries to keep track of them.""" self.hass = hass self.handlers_by_name = {} self.handlers_by_descriptor = {} - self.active_handlers_by_descriptor = {} + self.active_handlers_by_descriptor: dict[str, asyncio.Future] = {} + self.inotify = None self.watcher = None self.monitor_task = None @@ -110,16 +114,12 @@ class KeyboardRemote: connected, and start monitoring for device connection/disconnection. """ - # start watching - self.watcher = aionotify.Watcher() - self.watcher.watch( - alias="devinput", - path=DEVINPUT, - flags=aionotify.Flags.CREATE - | aionotify.Flags.ATTRIB - | aionotify.Flags.DELETE, + _LOGGER.debug("Start monitoring") + + self.inotify = Inotify() + self.watcher = self.inotify.add_watch( + DEVINPUT, Mask.CREATE | Mask.ATTRIB | Mask.DELETE ) - await self.watcher.setup(self.hass.loop) # add initial devices (do this AFTER starting watcher in order to # avoid race conditions leading to missing device connections) @@ -134,7 +134,9 @@ class KeyboardRemote: continue self.active_handlers_by_descriptor[descriptor] = handler - initial_start_monitoring.add(handler.async_start_monitoring(dev)) + initial_start_monitoring.add( + asyncio.create_task(handler.async_device_start_monitoring(dev)) + ) if initial_start_monitoring: await asyncio.wait(initial_start_monitoring) @@ -146,6 +148,10 @@ class KeyboardRemote: _LOGGER.debug("Cleanup on shutdown") + if self.inotify and self.watcher: + self.inotify.rm_watch(self.watcher) + self.watcher = None + if self.monitor_task is not None: if not self.monitor_task.done(): self.monitor_task.cancel() @@ -153,11 +159,16 @@ class KeyboardRemote: handler_stop_monitoring = set() for handler in self.active_handlers_by_descriptor.values(): - handler_stop_monitoring.add(handler.async_stop_monitoring()) - + handler_stop_monitoring.add( + asyncio.create_task(handler.async_device_stop_monitoring()) + ) if handler_stop_monitoring: await asyncio.wait(handler_stop_monitoring) + if self.inotify: + self.inotify.close() + self.inotify = None + def get_device_handler(self, descriptor): """Find the correct device handler given a descriptor (path).""" @@ -187,20 +198,21 @@ class KeyboardRemote: async def async_monitor_devices(self): """Monitor asynchronously for device connection/disconnection or permissions changes.""" + _LOGGER.debug("Start monitoring loop") + try: - while True: - event = await self.watcher.get_event() + async for event in self.inotify: descriptor = f"{DEVINPUT}/{event.name}" + _LOGGER.debug("got events for %s: %s", descriptor, event.mask) descriptor_active = descriptor in self.active_handlers_by_descriptor - if (event.flags & aionotify.Flags.DELETE) and descriptor_active: + if (event.mask & Mask.DELETE) and descriptor_active: handler = self.active_handlers_by_descriptor[descriptor] del self.active_handlers_by_descriptor[descriptor] - await handler.async_stop_monitoring() + await handler.async_device_stop_monitoring() elif ( - (event.flags & aionotify.Flags.CREATE) - or (event.flags & aionotify.Flags.ATTRIB) + (event.mask & Mask.CREATE) or (event.mask & Mask.ATTRIB) ) and not descriptor_active: dev, handler = await self.hass.async_add_executor_job( self.get_device_handler, descriptor @@ -208,31 +220,32 @@ class KeyboardRemote: if handler is None: continue self.active_handlers_by_descriptor[descriptor] = handler - await handler.async_start_monitoring(dev) + await handler.async_device_start_monitoring(dev) except asyncio.CancelledError: + _LOGGER.debug("Monitoring canceled") return class DeviceHandler: """Manage input events using evdev with asyncio.""" - def __init__(self, hass, dev_block): + def __init__(self, hass: HomeAssistant, dev_block: dict[str, Any]) -> None: """Fill configuration data.""" self.hass = hass - key_types = dev_block.get(TYPE) + key_types = dev_block[TYPE] self.key_values = set() for key_type in key_types: self.key_values.add(KEY_VALUE[key_type]) - self.emulate_key_hold = dev_block.get(EMULATE_KEY_HOLD) - self.emulate_key_hold_delay = dev_block.get(EMULATE_KEY_HOLD_DELAY) - self.emulate_key_hold_repeat = dev_block.get(EMULATE_KEY_HOLD_REPEAT) + self.emulate_key_hold = dev_block[EMULATE_KEY_HOLD] + self.emulate_key_hold_delay = dev_block[EMULATE_KEY_HOLD_DELAY] + self.emulate_key_hold_repeat = dev_block[EMULATE_KEY_HOLD_REPEAT] self.monitor_task = None self.dev = None - async def async_keyrepeat(self, path, name, code, delay, repeat): + async def async_device_keyrepeat(self, path, name, code, delay, repeat): """Emulate keyboard delay/repeat behaviour by sending key events on a timer.""" await asyncio.sleep(delay) @@ -248,8 +261,9 @@ class KeyboardRemote: ) await asyncio.sleep(repeat) - async def async_start_monitoring(self, dev): + async def async_device_start_monitoring(self, dev): """Start event monitoring task and issue event.""" + _LOGGER.debug("Keyboard async_device_start_monitoring, %s", dev.name) if self.monitor_task is None: self.dev = dev self.monitor_task = self.hass.async_create_task( @@ -261,7 +275,7 @@ class KeyboardRemote: ) _LOGGER.debug("Keyboard (re-)connected, %s", dev.name) - async def async_stop_monitoring(self): + async def async_device_stop_monitoring(self): """Stop event monitoring task and issue event.""" if self.monitor_task is not None: with suppress(OSError): @@ -295,6 +309,7 @@ class KeyboardRemote: _LOGGER.debug("Start device monitoring") await self.hass.async_add_executor_job(dev.grab) async for event in dev.async_read_loop(): + # pylint: disable=no-member if event.type is ecodes.EV_KEY: if event.value in self.key_values: _LOGGER.debug(categorize(event)) @@ -313,7 +328,7 @@ class KeyboardRemote: and self.emulate_key_hold ): repeat_tasks[event.code] = self.hass.async_create_task( - self.async_keyrepeat( + self.async_device_keyrepeat( dev.path, dev.name, event.code, diff --git a/homeassistant/components/keyboard_remote/manifest.json b/homeassistant/components/keyboard_remote/manifest.json index d319ba93ce2..2b298901ca9 100644 --- a/homeassistant/components/keyboard_remote/manifest.json +++ b/homeassistant/components/keyboard_remote/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/keyboard_remote", "iot_class": "local_push", "loggers": ["aionotify", "evdev"], - "requirements": ["evdev==1.4.0", "aionotify==0.2.0"] + "requirements": ["evdev==1.6.1", "asyncinotify==4.0.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 4f0e1957afa..7e62831c75e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -222,9 +222,6 @@ aiomusiccast==0.14.8 # homeassistant.components.nanoleaf aionanoleaf==0.2.1 -# homeassistant.components.keyboard_remote -aionotify==0.2.0 - # homeassistant.components.notion aionotion==2023.05.5 @@ -382,6 +379,9 @@ asterisk_mbox==0.5.0 # homeassistant.components.yeelight async-upnp-client==0.33.2 +# homeassistant.components.keyboard_remote +asyncinotify==4.0.2 + # homeassistant.components.supla asyncpysupla==0.0.5 @@ -695,7 +695,7 @@ eternalegypt==0.0.16 eufylife_ble_client==0.1.7 # homeassistant.components.keyboard_remote -# evdev==1.4.0 +# evdev==1.6.1 # homeassistant.components.evohome evohome-async==0.3.15 From 8f437c5833e28743adff51ba66a46d34f3aab1f5 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Wed, 14 Jun 2023 02:07:03 -0500 Subject: [PATCH 53/83] Fix failed recovery in roku (#94572) --- homeassistant/components/roku/__init__.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/roku/__init__.py b/homeassistant/components/roku/__init__.py index 7f922c2eea5..583d26a4a5b 100644 --- a/homeassistant/components/roku/__init__.py +++ b/homeassistant/components/roku/__init__.py @@ -22,13 +22,11 @@ PLATFORMS = [ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Roku from a config entry.""" - hass.data.setdefault(DOMAIN, {}) - if not (coordinator := hass.data[DOMAIN].get(entry.entry_id)): - coordinator = RokuDataUpdateCoordinator(hass, host=entry.data[CONF_HOST]) - hass.data[DOMAIN][entry.entry_id] = coordinator - + coordinator = RokuDataUpdateCoordinator(hass, host=entry.data[CONF_HOST]) await coordinator.async_config_entry_first_refresh() + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True @@ -36,7 +34,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" - unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - if unload_ok: + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): hass.data[DOMAIN].pop(entry.entry_id) return unload_ok From e6b8e4fd48c06b1264e3ed7ca24b38f4b9c7c0cb Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Wed, 14 Jun 2023 02:07:24 -0500 Subject: [PATCH 54/83] Fix failed recovery in ipp (#94573) --- homeassistant/components/ipp/__init__.py | 26 ++++++++++-------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/ipp/__init__.py b/homeassistant/components/ipp/__init__.py index 42dc2b8d93b..9df377b939a 100644 --- a/homeassistant/components/ipp/__init__.py +++ b/homeassistant/components/ipp/__init__.py @@ -19,21 +19,18 @@ PLATFORMS = [Platform.SENSOR] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up IPP from a config entry.""" - hass.data.setdefault(DOMAIN, {}) - if not (coordinator := hass.data[DOMAIN].get(entry.entry_id)): - # Create IPP instance for this entry - coordinator = IPPDataUpdateCoordinator( - hass, - host=entry.data[CONF_HOST], - port=entry.data[CONF_PORT], - base_path=entry.data[CONF_BASE_PATH], - tls=entry.data[CONF_SSL], - verify_ssl=entry.data[CONF_VERIFY_SSL], - ) - hass.data[DOMAIN][entry.entry_id] = coordinator - + coordinator = IPPDataUpdateCoordinator( + hass, + host=entry.data[CONF_HOST], + port=entry.data[CONF_PORT], + base_path=entry.data[CONF_BASE_PATH], + tls=entry.data[CONF_SSL], + verify_ssl=entry.data[CONF_VERIFY_SSL], + ) await coordinator.async_config_entry_first_refresh() + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True @@ -41,7 +38,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" - unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - if unload_ok: + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): hass.data[DOMAIN].pop(entry.entry_id) return unload_ok From 9cbcfca2cd7209bf4a4a0c09515cf38511b903ca Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 14 Jun 2023 10:26:54 +0200 Subject: [PATCH 55/83] Improve multipan debug logging (#94580) --- .../silabs_multiprotocol_addon.py | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py b/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py index 34ab9a3cedb..c5f7049e54f 100644 --- a/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py +++ b/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py @@ -96,19 +96,29 @@ class MultiprotocolAddonManager(AddonManager): ) -> None: """Register a multipan platform.""" self._platforms[integration_domain] = platform - if self._channel is not None or not await platform.async_using_multipan(hass): + + channel = await platform.async_get_channel(hass) + using_multipan = await platform.async_using_multipan(hass) + + _LOGGER.info( + "Registering new multipan platform '%s', using multipan: %s, channel: %s", + integration_domain, + using_multipan, + channel, + ) + + if self._channel is not None or not using_multipan: return - new_channel = await platform.async_get_channel(hass) - if new_channel is None: + if channel is None: return _LOGGER.info( "Setting multipan channel to %s (source: '%s')", - new_channel, + channel, integration_domain, ) - self.async_set_channel(new_channel) + self.async_set_channel(channel) async def async_change_channel( self, channel: int, delay: float From 905bdd0dd5bf94854e6b33005ca771cfda552fea Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 14 Jun 2023 21:18:40 -0400 Subject: [PATCH 56/83] Bumped version to 2023.6.2 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 3392b6dabd8..2d5e5ebb14c 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -8,7 +8,7 @@ from .backports.enum import StrEnum APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2023 MINOR_VERSION: Final = 6 -PATCH_VERSION: Final = "1" +PATCH_VERSION: Final = "2" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 10, 0) diff --git a/pyproject.toml b/pyproject.toml index ca4f61a5ccc..ff8898aa90a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2023.6.1" +version = "2023.6.2" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 3f936993a906eccdda61af9be2d9dec07fcd6107 Mon Sep 17 00:00:00 2001 From: Mike Heath Date: Mon, 19 Jun 2023 05:12:04 -0600 Subject: [PATCH 57/83] Register Fully Kiosk services regardless of setup result (#88647) * Register services at integration level If HA is unable to connect to Fully Kiosk, the services don't get registered. This can cause repair to create notifications saying that the 'fully_kiosk.load_url' service is unknown. Fixes #85444 * Validate config entry is loaded * Refactor service invocation Raises `HomeAssistantError` when the user provides an device id that is not in the device registry or a device that is not a Fully Kiosk device. If the device's config entry is not loaded, a warning is logged. * Update homeassistant/components/fully_kiosk/services.py Co-authored-by: Martin Hjelmare * Assert HomeAssistantError when integration unloaded * Remove unused import * Set CONFIG_SCHEMA * Update homeassistant/components/fully_kiosk/__init__.py Co-authored-by: Martin Hjelmare * Add test for non fkb devices targets in service calls * Apply suggestions from code review --------- Co-authored-by: Martin Hjelmare --- .../components/fully_kiosk/__init__.py | 14 ++- .../components/fully_kiosk/services.py | 70 ++++++------ tests/components/fully_kiosk/test_services.py | 102 +++++++++++++++++- 3 files changed, 141 insertions(+), 45 deletions(-) diff --git a/homeassistant/components/fully_kiosk/__init__.py b/homeassistant/components/fully_kiosk/__init__.py index dd1cc70c9f4..8b350433858 100644 --- a/homeassistant/components/fully_kiosk/__init__.py +++ b/homeassistant/components/fully_kiosk/__init__.py @@ -2,6 +2,8 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.typing import ConfigType from .const import DOMAIN from .coordinator import FullyKioskDataUpdateCoordinator @@ -16,6 +18,16 @@ PLATFORMS = [ Platform.SWITCH, ] +CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN) + + +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up Fully Kiosk Browser.""" + + await async_setup_services(hass) + + return True + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Fully Kiosk Browser from a config entry.""" @@ -28,8 +40,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) coordinator.async_update_listeners() - await async_setup_services(hass) - return True diff --git a/homeassistant/components/fully_kiosk/services.py b/homeassistant/components/fully_kiosk/services.py index 3fca9228735..b3c5886187a 100644 --- a/homeassistant/components/fully_kiosk/services.py +++ b/homeassistant/components/fully_kiosk/services.py @@ -1,14 +1,12 @@ """Services for the Fully Kiosk Browser integration.""" from __future__ import annotations -from collections.abc import Callable -from typing import Any - -from fullykiosk import FullyKiosk import voluptuous as vol +from homeassistant.config_entries import ConfigEntry, ConfigEntryState from homeassistant.const import ATTR_DEVICE_ID from homeassistant.core import HomeAssistant, ServiceCall +from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv import homeassistant.helpers.device_registry as dr @@ -16,59 +14,53 @@ from .const import ( ATTR_APPLICATION, ATTR_URL, DOMAIN, - LOGGER, SERVICE_LOAD_URL, SERVICE_START_APPLICATION, ) +from .coordinator import FullyKioskDataUpdateCoordinator async def async_setup_services(hass: HomeAssistant) -> None: """Set up the services for the Fully Kiosk Browser integration.""" - async def execute_service( - call: ServiceCall, - fully_method: Callable, - *args: list[str], - **kwargs: dict[str, Any], - ) -> None: - """Execute a Fully service call. - - :param call: {ServiceCall} HA service call. - :param fully_method: {Callable} A method of the FullyKiosk class. - :param args: Arguments for fully_method. - :param kwargs: Key-word arguments for fully_method. - :return: None - """ - LOGGER.debug( - "Calling Fully service %s with args: %s, %s", ServiceCall, args, kwargs - ) + async def collect_coordinators( + device_ids: list[str], + ) -> list[FullyKioskDataUpdateCoordinator]: + config_entries = list[ConfigEntry]() registry = dr.async_get(hass) - for target in call.data[ATTR_DEVICE_ID]: + for target in device_ids: device = registry.async_get(target) if device: - for key in device.config_entries: - entry = hass.config_entries.async_get_entry(key) - if not entry: - continue - if entry.domain != DOMAIN: - continue - coordinator = hass.data[DOMAIN][key] - # fully_method(coordinator.fully, *args, **kwargs) would make - # test_services.py fail. - await getattr(coordinator.fully, fully_method.__name__)( - *args, **kwargs + device_entries = list[ConfigEntry]() + for entry_id in device.config_entries: + entry = hass.config_entries.async_get_entry(entry_id) + if entry and entry.domain == DOMAIN: + device_entries.append(entry) + if not device_entries: + raise HomeAssistantError( + f"Device '{target}' is not a {DOMAIN} device" ) - break + config_entries.extend(device_entries) + else: + raise HomeAssistantError( + f"Device '{target}' not found in device registry" + ) + coordinators = list[FullyKioskDataUpdateCoordinator]() + for config_entry in config_entries: + if config_entry.state != ConfigEntryState.LOADED: + raise HomeAssistantError(f"{config_entry.title} is not loaded") + coordinators.append(hass.data[DOMAIN][config_entry.entry_id]) + return coordinators async def async_load_url(call: ServiceCall) -> None: """Load a URL on the Fully Kiosk Browser.""" - await execute_service(call, FullyKiosk.loadUrl, call.data[ATTR_URL]) + for coordinator in await collect_coordinators(call.data[ATTR_DEVICE_ID]): + await coordinator.fully.loadUrl(call.data[ATTR_URL]) async def async_start_app(call: ServiceCall) -> None: """Start an app on the device.""" - await execute_service( - call, FullyKiosk.startApplication, call.data[ATTR_APPLICATION] - ) + for coordinator in await collect_coordinators(call.data[ATTR_DEVICE_ID]): + await coordinator.fully.startApplication(call.data[ATTR_APPLICATION]) # Register all the above services service_mapping = [ diff --git a/tests/components/fully_kiosk/test_services.py b/tests/components/fully_kiosk/test_services.py index 386bc542e3c..504aa4893e6 100644 --- a/tests/components/fully_kiosk/test_services.py +++ b/tests/components/fully_kiosk/test_services.py @@ -1,6 +1,8 @@ """Test Fully Kiosk Browser services.""" from unittest.mock import MagicMock +import pytest + from homeassistant.components.fully_kiosk.const import ( ATTR_APPLICATION, ATTR_URL, @@ -10,6 +12,7 @@ from homeassistant.components.fully_kiosk.const import ( ) from homeassistant.const import ATTR_DEVICE_ID from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr from tests.common import MockConfigEntry @@ -28,20 +31,111 @@ async def test_services( assert device_entry + url = "https://example.com" await hass.services.async_call( DOMAIN, SERVICE_LOAD_URL, - {ATTR_DEVICE_ID: [device_entry.id], ATTR_URL: "https://example.com"}, + {ATTR_DEVICE_ID: [device_entry.id], ATTR_URL: url}, blocking=True, ) - assert len(mock_fully_kiosk.loadUrl.mock_calls) == 1 + mock_fully_kiosk.loadUrl.assert_called_once_with(url) + app = "de.ozerov.fully" await hass.services.async_call( DOMAIN, SERVICE_START_APPLICATION, - {ATTR_DEVICE_ID: [device_entry.id], ATTR_APPLICATION: "de.ozerov.fully"}, + {ATTR_DEVICE_ID: [device_entry.id], ATTR_APPLICATION: app}, blocking=True, ) - assert len(mock_fully_kiosk.startApplication.mock_calls) == 1 + mock_fully_kiosk.startApplication.assert_called_once_with(app) + + +async def test_service_unloaded_entry( + hass: HomeAssistant, + mock_fully_kiosk: MagicMock, + init_integration: MockConfigEntry, +) -> None: + """Test service not called when config entry unloaded.""" + await init_integration.async_unload(hass) + + device_registry = dr.async_get(hass) + device_entry = device_registry.async_get_device( + identifiers={(DOMAIN, "abcdef-123456")} + ) + + assert device_entry + + with pytest.raises(HomeAssistantError) as excinfo: + await hass.services.async_call( + DOMAIN, + SERVICE_LOAD_URL, + {ATTR_DEVICE_ID: [device_entry.id], ATTR_URL: "https://nabucasa.com"}, + blocking=True, + ) + assert "Test device is not loaded" in str(excinfo) + mock_fully_kiosk.loadUrl.assert_not_called() + + with pytest.raises(HomeAssistantError) as excinfo: + await hass.services.async_call( + DOMAIN, + SERVICE_START_APPLICATION, + {ATTR_DEVICE_ID: [device_entry.id], ATTR_APPLICATION: "de.ozerov.fully"}, + blocking=True, + ) + assert "Test device is not loaded" in str(excinfo) + mock_fully_kiosk.startApplication.assert_not_called() + + +async def test_service_bad_device_id( + hass: HomeAssistant, + mock_fully_kiosk: MagicMock, + init_integration: MockConfigEntry, +) -> None: + """Test Fully Kiosk Browser service invocation with bad device id.""" + with pytest.raises(HomeAssistantError) as excinfo: + await hass.services.async_call( + DOMAIN, + SERVICE_LOAD_URL, + {ATTR_DEVICE_ID: ["bad-device_id"], ATTR_URL: "https://example.com"}, + blocking=True, + ) + + assert "Device 'bad-device_id' not found in device registry" in str(excinfo) + + +async def test_service_called_with_non_fkb_target_devices( + hass: HomeAssistant, + mock_fully_kiosk: MagicMock, + init_integration: MockConfigEntry, +) -> None: + """Services raise exception when no valid devices provided.""" + device_registry = dr.async_get(hass) + + other_domain = "NotFullyKiosk" + other_config_id = "555" + await hass.config_entries.async_add( + MockConfigEntry( + title="Not Fully Kiosk", domain=other_domain, entry_id=other_config_id + ) + ) + device_entry = device_registry.async_get_or_create( + config_entry_id=other_config_id, + identifiers={ + (other_domain, 1), + }, + ) + + with pytest.raises(HomeAssistantError) as excinfo: + await hass.services.async_call( + DOMAIN, + SERVICE_LOAD_URL, + { + ATTR_DEVICE_ID: [device_entry.id], + ATTR_URL: "https://example.com", + }, + blocking=True, + ) + + assert f"Device '{device_entry.id}' is not a fully_kiosk device" in str(excinfo) From bd0b8dc0bc5434adc216d169152c026fdad0a58b Mon Sep 17 00:00:00 2001 From: Kim Frellsen Date: Thu, 15 Jun 2023 15:34:14 +0200 Subject: [PATCH 58/83] Fortios device tracker updates (#92331) Co-authored-by: Martin Hjelmare Co-authored-by: Erik Montnemery --- .../components/fortios/device_tracker.py | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/fortios/device_tracker.py b/homeassistant/components/fortios/device_tracker.py index 95a418ae40f..d941375c8a3 100644 --- a/homeassistant/components/fortios/device_tracker.py +++ b/homeassistant/components/fortios/device_tracker.py @@ -43,7 +43,7 @@ def get_scanner(hass: HomeAssistant, config: ConfigType) -> FortiOSDeviceScanner fgt = FortiOSAPI() try: - fgt.tokenlogin(host, token, verify_ssl) + fgt.tokenlogin(host, token, verify_ssl, None, 12, "root") except ConnectionError as ex: _LOGGER.error("ConnectionError to FortiOS API: %s", ex) return None @@ -77,7 +77,12 @@ class FortiOSDeviceScanner(DeviceScanner): def update(self): """Update clients from the device.""" - clients_json = self._fgt.monitor("user/device/query", "") + clients_json = self._fgt.monitor( + "user/device/query", + "", + parameters={"filter": "format=master_mac|hostname|is_online"}, + ) + self._clients_json = clients_json self._clients = [] @@ -85,8 +90,12 @@ class FortiOSDeviceScanner(DeviceScanner): if clients_json: try: for client in clients_json["results"]: - if client["is_online"]: - self._clients.append(client["mac"].upper()) + if ( + "is_online" in client + and "master_mac" in client + and client["is_online"] + ): + self._clients.append(client["master_mac"].upper()) except KeyError as kex: _LOGGER.error("Key not found in clients: %s", kex) @@ -106,17 +115,10 @@ class FortiOSDeviceScanner(DeviceScanner): return None for client in data["results"]: - if client["mac"] == device: - try: + if "master_mac" in client and client["master_mac"] == device: + if "hostname" in client: name = client["hostname"] - _LOGGER.debug("Getting device name=%s", name) - return name - except KeyError as kex: - _LOGGER.debug( - "No hostname found for %s in client data: %s", - device, - kex, - ) - return device.replace(":", "_") - + else: + name = client["master_mac"].replace(":", "_") + return name return None From d2385f97a78197a9f97c2325dba35ea786d5107a Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 23 Jun 2023 15:34:37 +0200 Subject: [PATCH 59/83] Handle LastFM unavailable (#94456) --- homeassistant/components/lastfm/sensor.py | 36 +++++++++++++---------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/lastfm/sensor.py b/homeassistant/components/lastfm/sensor.py index d8cf96be5ac..08179df5b7e 100644 --- a/homeassistant/components/lastfm/sensor.py +++ b/homeassistant/components/lastfm/sensor.py @@ -3,7 +3,7 @@ from __future__ import annotations import hashlib -from pylast import LastFMNetwork, Track, User, WSError +from pylast import LastFMNetwork, PyLastError, Track, User import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity @@ -104,26 +104,30 @@ class LastFmSensor(SensorEntity): def update(self) -> None: """Update device state.""" + self._attr_native_value = STATE_NOT_SCROBBLING try: - self._user.get_playcount() - except WSError as exc: + play_count = self._user.get_playcount() + self._attr_entity_picture = self._user.get_image() + now_playing = self._user.get_now_playing() + top_tracks = self._user.get_top_tracks(limit=1) + last_tracks = self._user.get_recent_tracks(limit=1) + except PyLastError as exc: self._attr_available = False LOGGER.error("Failed to load LastFM user `%s`: %r", self._user.name, exc) return - self._attr_entity_picture = self._user.get_image() - if now_playing := self._user.get_now_playing(): + self._attr_available = True + if now_playing: self._attr_native_value = format_track(now_playing) - else: - self._attr_native_value = STATE_NOT_SCROBBLING - top_played = None - if top_tracks := self._user.get_top_tracks(limit=1): - top_played = format_track(top_tracks[0].item) - last_played = None - if last_tracks := self._user.get_recent_tracks(limit=1): - last_played = format_track(last_tracks[0].track) - play_count = self._user.get_playcount() self._attr_extra_state_attributes = { - ATTR_LAST_PLAYED: last_played, ATTR_PLAY_COUNT: play_count, - ATTR_TOP_PLAYED: top_played, + ATTR_LAST_PLAYED: None, + ATTR_TOP_PLAYED: None, } + if len(last_tracks) > 0: + self._attr_extra_state_attributes[ATTR_LAST_PLAYED] = format_track( + last_tracks[0].track + ) + if len(top_tracks) > 0: + self._attr_extra_state_attributes[ATTR_TOP_PLAYED] = format_track( + top_tracks[0].item + ) From ffe35c73b63fcc1e5d1f1b65710645ab24a01944 Mon Sep 17 00:00:00 2001 From: Tom Harris Date: Fri, 16 Jun 2023 03:35:44 -0400 Subject: [PATCH 60/83] Handle Insteon events correctly (#94549) Make events generalized --- homeassistant/components/insteon/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/insteon/utils.py b/homeassistant/components/insteon/utils.py index f9c22ef62a5..d7cbe676eee 100644 --- a/homeassistant/components/insteon/utils.py +++ b/homeassistant/components/insteon/utils.py @@ -115,8 +115,8 @@ def add_insteon_events(hass: HomeAssistant, device: Device) -> None: """Register Insteon device events.""" @callback - def async_fire_group_on_off_event( - name: str, address: Address, group: int, button: str + def async_fire_insteon_event( + name: str, address: Address, group: int, button: str | None = None ): # Firing an event when a button is pressed. if button and button[-2] == "_": @@ -146,9 +146,9 @@ def add_insteon_events(hass: HomeAssistant, device: Device) -> None: for name_or_group, event in device.events.items(): if isinstance(name_or_group, int): for _, event in device.events[name_or_group].items(): - _register_event(event, async_fire_group_on_off_event) + _register_event(event, async_fire_insteon_event) else: - _register_event(event, async_fire_group_on_off_event) + _register_event(event, async_fire_insteon_event) def register_new_device_callback(hass): From 78bbec0a6e20467908f2821c0c0e62494d478f6b Mon Sep 17 00:00:00 2001 From: Alistair Tudor <3691326+atudor2@users.noreply.github.com> Date: Fri, 16 Jun 2023 09:33:02 +0200 Subject: [PATCH 61/83] Fix unit for Habitica text sensors (#94550) --- homeassistant/components/habitica/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/habitica/sensor.py b/homeassistant/components/habitica/sensor.py index e085167301f..d9e0fb227c0 100644 --- a/homeassistant/components/habitica/sensor.py +++ b/homeassistant/components/habitica/sensor.py @@ -24,7 +24,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15) SensorType = namedtuple("SensorType", ["name", "icon", "unit", "path"]) SENSORS_TYPES = { - "name": SensorType("Name", None, "", ["profile", "name"]), + "name": SensorType("Name", None, None, ["profile", "name"]), "hp": SensorType("HP", "mdi:heart", "HP", ["stats", "hp"]), "maxHealth": SensorType("max HP", "mdi:heart", "HP", ["stats", "maxHealth"]), "mp": SensorType("Mana", "mdi:auto-fix", "MP", ["stats", "mp"]), @@ -35,7 +35,7 @@ SENSORS_TYPES = { "Lvl", "mdi:arrow-up-bold-circle-outline", "Lvl", ["stats", "lvl"] ), "gp": SensorType("Gold", "mdi:circle-multiple", "Gold", ["stats", "gp"]), - "class": SensorType("Class", "mdi:sword", "", ["stats", "class"]), + "class": SensorType("Class", "mdi:sword", None, ["stats", "class"]), } TASKS_TYPES = { From e1c486fc4a82111c5747b93d219bfecce8755ea2 Mon Sep 17 00:00:00 2001 From: Dominik Date: Thu, 15 Jun 2023 12:30:52 +0200 Subject: [PATCH 62/83] Bump minimum typing_extensions to 4.6.3 (#94587) --- homeassistant/package_constraints.txt | 2 +- pyproject.toml | 2 +- requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index a7688415905..3c4fc9a6746 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -46,7 +46,7 @@ pyyaml==6.0 requests==2.31.0 scapy==2.5.0 sqlalchemy==2.0.15 -typing-extensions>=4.5.0,<5.0 +typing_extensions>=4.6.3,<5.0 ulid-transform==0.7.2 voluptuous-serialize==2.6.0 voluptuous==0.13.1 diff --git a/pyproject.toml b/pyproject.toml index ff8898aa90a..5f3c1b4f71e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ dependencies = [ "python-slugify==4.0.1", "pyyaml==6.0", "requests==2.31.0", - "typing-extensions>=4.5.0,<5.0", + "typing_extensions>=4.6.3,<5.0", "ulid-transform==0.7.2", "voluptuous==0.13.1", "voluptuous-serialize==2.6.0", diff --git a/requirements.txt b/requirements.txt index 818eeec8515..48dfff5d662 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,7 +23,7 @@ pip>=21.0,<23.2 python-slugify==4.0.1 pyyaml==6.0 requests==2.31.0 -typing-extensions>=4.5.0,<5.0 +typing_extensions>=4.6.3,<5.0 ulid-transform==0.7.2 voluptuous==0.13.1 voluptuous-serialize==2.6.0 From 203820d836094b9f378b03329ad8ab84df6b4880 Mon Sep 17 00:00:00 2001 From: Dominik Date: Fri, 23 Jun 2023 14:36:43 +0200 Subject: [PATCH 63/83] Fix glances raid plugin data (#94597) Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> --- homeassistant/components/glances/sensor.py | 54 +++++++++++----------- tests/components/glances/__init__.py | 52 +++++++++++++++++++++ tests/components/glances/test_sensor.py | 8 ++++ 3 files changed, 87 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/glances/sensor.py b/homeassistant/components/glances/sensor.py index f4a3f882749..e952164792f 100644 --- a/homeassistant/components/glances/sensor.py +++ b/homeassistant/components/glances/sensor.py @@ -223,13 +223,6 @@ SENSOR_TYPES = { icon="mdi:docker", state_class=SensorStateClass.MEASUREMENT, ), - ("raid", "used"): GlancesSensorEntityDescription( - key="used", - type="raid", - name_suffix="Raid used", - icon="mdi:harddisk", - state_class=SensorStateClass.MEASUREMENT, - ), ("raid", "available"): GlancesSensorEntityDescription( key="available", type="raid", @@ -237,6 +230,13 @@ SENSOR_TYPES = { icon="mdi:harddisk", state_class=SensorStateClass.MEASUREMENT, ), + ("raid", "used"): GlancesSensorEntityDescription( + key="used", + type="raid", + name_suffix="Raid used", + icon="mdi:harddisk", + state_class=SensorStateClass.MEASUREMENT, + ), } @@ -269,36 +269,36 @@ async def async_setup_entry( if sensor_type in ["fs", "sensors", "raid"]: for sensor_label, params in sensors.items(): for param in params: - sensor_description = SENSOR_TYPES[(sensor_type, param)] + if sensor_description := SENSOR_TYPES.get((sensor_type, param)): + _migrate_old_unique_ids( + hass, + f"{coordinator.host}-{name} {sensor_label} {sensor_description.name_suffix}", + f"{sensor_label}-{sensor_description.key}", + ) + entities.append( + GlancesSensor( + coordinator, + name, + sensor_label, + sensor_description, + ) + ) + else: + for sensor in sensors: + if sensor_description := SENSOR_TYPES.get((sensor_type, sensor)): _migrate_old_unique_ids( hass, - f"{coordinator.host}-{name} {sensor_label} {sensor_description.name_suffix}", - f"{sensor_label}-{sensor_description.key}", + f"{coordinator.host}-{name} {sensor_description.name_suffix}", + f"-{sensor_description.key}", ) entities.append( GlancesSensor( coordinator, name, - sensor_label, + "", sensor_description, ) ) - else: - for sensor in sensors: - sensor_description = SENSOR_TYPES[(sensor_type, sensor)] - _migrate_old_unique_ids( - hass, - f"{coordinator.host}-{name} {sensor_description.name_suffix}", - f"-{sensor_description.key}", - ) - entities.append( - GlancesSensor( - coordinator, - name, - "", - sensor_description, - ) - ) async_add_entities(entities) diff --git a/tests/components/glances/__init__.py b/tests/components/glances/__init__.py index 064c5ab0eb5..41f2675c41c 100644 --- a/tests/components/glances/__init__.py +++ b/tests/components/glances/__init__.py @@ -137,6 +137,40 @@ MOCK_DATA = { "os_version": "5.15.6-200.fc35.x86_64", "hr_name": "Fedora Linux 35 64bit", }, + "raid": { + "md3": { + "status": "active", + "type": "raid1", + "components": {"sdh1": "2", "sdi1": "0"}, + "available": "2", + "used": "2", + "config": "UU", + }, + "md1": { + "status": "active", + "type": "raid1", + "components": {"sdg": "0", "sde": "1"}, + "available": "2", + "used": "2", + "config": "UU", + }, + "md4": { + "status": "active", + "type": "raid1", + "components": {"sdf1": "1", "sdb1": "0"}, + "available": "2", + "used": "2", + "config": "UU", + }, + "md0": { + "status": "active", + "type": "raid1", + "components": {"sdc": "2", "sdd": "3"}, + "available": "2", + "used": "2", + "config": "UU", + }, + }, "uptime": "3 days, 10:25:20", } @@ -156,4 +190,22 @@ HA_SENSOR_DATA: dict[str, Any] = { "memory_free": 2745.0, }, "docker": {"docker_active": 2, "docker_cpu_use": 77.2, "docker_memory_use": 1149.6}, + "raid": { + "md3": { + "status": "active", + "type": "raid1", + "components": {"sdh1": "2", "sdi1": "0"}, + "available": "2", + "used": "2", + "config": "UU", + }, + "md1": { + "status": "active", + "type": "raid1", + "components": {"sdg": "0", "sde": "1"}, + "available": "2", + "used": "2", + "config": "UU", + }, + }, } diff --git a/tests/components/glances/test_sensor.py b/tests/components/glances/test_sensor.py index 2366e10d11b..d7705854720 100644 --- a/tests/components/glances/test_sensor.py +++ b/tests/components/glances/test_sensor.py @@ -35,6 +35,14 @@ async def test_sensor_states(hass: HomeAssistant) -> None: assert state.state == HA_SENSOR_DATA["docker"]["docker_cpu_use"] if state := hass.states.get("sensor.0_0_0_0_docker_memory_use"): assert state.state == HA_SENSOR_DATA["docker"]["docker_memory_use"] + if state := hass.states.get("sensor.0_0_0_0_md3_available"): + assert state.state == HA_SENSOR_DATA["raid"]["md3"]["available"] + if state := hass.states.get("sensor.0_0_0_0_md3_used"): + assert state.state == HA_SENSOR_DATA["raid"]["md3"]["used"] + if state := hass.states.get("sensor.0_0_0_0_md1_available"): + assert state.state == HA_SENSOR_DATA["raid"]["md1"]["available"] + if state := hass.states.get("sensor.0_0_0_0_md1_used"): + assert state.state == HA_SENSOR_DATA["raid"]["md1"]["used"] @pytest.mark.parametrize( From 57dd62e7d6d22f47a0392d2afa907a2417a0fc12 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 18 Jun 2023 03:59:06 +0200 Subject: [PATCH 64/83] Make YouTube select lower quality thumbnails (#94652) * Make YouTube select lower quality thumbnails * Make YouTube select lower quality thumbnails * Make YouTube select lower quality thumbnails * Make YouTube select lower quality thumbnails * Add tests * Add tests * Add tests * Add tests * Add tests --- .../components/youtube/coordinator.py | 9 ++- homeassistant/components/youtube/sensor.py | 4 +- .../youtube/fixtures/thumbnail/default.json | 42 ++++++++++++++ .../youtube/fixtures/thumbnail/high.json | 52 +++++++++++++++++ .../youtube/fixtures/thumbnail/medium.json | 47 +++++++++++++++ .../youtube/fixtures/thumbnail/none.json | 36 ++++++++++++ .../youtube/fixtures/thumbnail/standard.json | 57 +++++++++++++++++++ tests/components/youtube/test_sensor.py | 36 ++++++++++++ 8 files changed, 280 insertions(+), 3 deletions(-) create mode 100644 tests/components/youtube/fixtures/thumbnail/default.json create mode 100644 tests/components/youtube/fixtures/thumbnail/high.json create mode 100644 tests/components/youtube/fixtures/thumbnail/medium.json create mode 100644 tests/components/youtube/fixtures/thumbnail/none.json create mode 100644 tests/components/youtube/fixtures/thumbnail/standard.json diff --git a/homeassistant/components/youtube/coordinator.py b/homeassistant/components/youtube/coordinator.py index 190e79e3393..d3430d8329c 100644 --- a/homeassistant/components/youtube/coordinator.py +++ b/homeassistant/components/youtube/coordinator.py @@ -85,9 +85,16 @@ class YouTubeDataUpdateCoordinator(DataUpdateCoordinator): ATTR_PUBLISHED_AT: video["snippet"]["publishedAt"], ATTR_TITLE: video["snippet"]["title"], ATTR_DESCRIPTION: video["snippet"]["description"], - ATTR_THUMBNAIL: video["snippet"]["thumbnails"]["standard"]["url"], + ATTR_THUMBNAIL: self._get_thumbnail(video), ATTR_VIDEO_ID: video["contentDetails"]["videoId"], }, ATTR_SUBSCRIBER_COUNT: int(channel["statistics"]["subscriberCount"]), } return data + + def _get_thumbnail(self, video: dict[str, Any]) -> str | None: + thumbnails = video["snippet"]["thumbnails"] + for size in ("standard", "high", "medium", "default"): + if size in thumbnails: + return thumbnails[size]["url"] + return None diff --git a/homeassistant/components/youtube/sensor.py b/homeassistant/components/youtube/sensor.py index c605b960475..4560dcfda8c 100644 --- a/homeassistant/components/youtube/sensor.py +++ b/homeassistant/components/youtube/sensor.py @@ -30,7 +30,7 @@ class YouTubeMixin: """Mixin for required keys.""" value_fn: Callable[[Any], StateType] - entity_picture_fn: Callable[[Any], str] + entity_picture_fn: Callable[[Any], str | None] attributes_fn: Callable[[Any], dict[str, Any]] | None @@ -87,7 +87,7 @@ class YouTubeSensor(YouTubeChannelEntity, SensorEntity): return self.entity_description.value_fn(self.coordinator.data[self._channel_id]) @property - def entity_picture(self) -> str: + def entity_picture(self) -> str | None: """Return the value reported by the sensor.""" return self.entity_description.entity_picture_fn( self.coordinator.data[self._channel_id] diff --git a/tests/components/youtube/fixtures/thumbnail/default.json b/tests/components/youtube/fixtures/thumbnail/default.json new file mode 100644 index 00000000000..6b5d66d6501 --- /dev/null +++ b/tests/components/youtube/fixtures/thumbnail/default.json @@ -0,0 +1,42 @@ +{ + "kind": "youtube#playlistItemListResponse", + "etag": "O0Ah8Wd5pUD2Gsv-n0A42RDRcX8", + "nextPageToken": "EAAaBlBUOkNBVQ", + "items": [ + { + "kind": "youtube#playlistItem", + "etag": "qgpoAJRNskzLhD99njC8e2kPB0M", + "id": "VVVfeDVYRzFPVjJQNnVaWjVGU005VHR3Lnd5c3VrRHJNZHFV", + "snippet": { + "publishedAt": "2023-05-11T00:20:46Z", + "channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw", + "title": "What's new in Google Home in less than 1 minute", + "description": "Discover how your connected devices can do more with Google Home using Matter and Automations at Google I/O 2023.\n\nTo learn more about what's new in Google Home, check out the keynote → https://goo.gle/IO23_homekey\n\nSubscribe to Google Developers → https://goo.gle/developers \n\n#GoogleIO #GoogleHome", + "thumbnails": { + "default": { + "url": "https://i.ytimg.com/vi/wysukDrMdqU/default.jpg", + "width": 120, + "height": 90 + } + }, + "channelTitle": "Google for Developers", + "playlistId": "UU_x5XG1OV2P6uZZ5FSM9Ttw", + "position": 0, + "resourceId": { + "kind": "youtube#video", + "videoId": "wysukDrMdqU" + }, + "videoOwnerChannelTitle": "Google for Developers", + "videoOwnerChannelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw" + }, + "contentDetails": { + "videoId": "wysukDrMdqU", + "videoPublishedAt": "2023-05-11T00:20:46Z" + } + } + ], + "pageInfo": { + "totalResults": 5798, + "resultsPerPage": 1 + } +} diff --git a/tests/components/youtube/fixtures/thumbnail/high.json b/tests/components/youtube/fixtures/thumbnail/high.json new file mode 100644 index 00000000000..430ad3715cc --- /dev/null +++ b/tests/components/youtube/fixtures/thumbnail/high.json @@ -0,0 +1,52 @@ +{ + "kind": "youtube#playlistItemListResponse", + "etag": "O0Ah8Wd5pUD2Gsv-n0A42RDRcX8", + "nextPageToken": "EAAaBlBUOkNBVQ", + "items": [ + { + "kind": "youtube#playlistItem", + "etag": "qgpoAJRNskzLhD99njC8e2kPB0M", + "id": "VVVfeDVYRzFPVjJQNnVaWjVGU005VHR3Lnd5c3VrRHJNZHFV", + "snippet": { + "publishedAt": "2023-05-11T00:20:46Z", + "channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw", + "title": "What's new in Google Home in less than 1 minute", + "description": "Discover how your connected devices can do more with Google Home using Matter and Automations at Google I/O 2023.\n\nTo learn more about what's new in Google Home, check out the keynote → https://goo.gle/IO23_homekey\n\nSubscribe to Google Developers → https://goo.gle/developers \n\n#GoogleIO #GoogleHome", + "thumbnails": { + "default": { + "url": "https://i.ytimg.com/vi/wysukDrMdqU/default.jpg", + "width": 120, + "height": 90 + }, + "medium": { + "url": "https://i.ytimg.com/vi/wysukDrMdqU/mqdefault.jpg", + "width": 320, + "height": 180 + }, + "high": { + "url": "https://i.ytimg.com/vi/wysukDrMdqU/hqdefault.jpg", + "width": 480, + "height": 360 + } + }, + "channelTitle": "Google for Developers", + "playlistId": "UU_x5XG1OV2P6uZZ5FSM9Ttw", + "position": 0, + "resourceId": { + "kind": "youtube#video", + "videoId": "wysukDrMdqU" + }, + "videoOwnerChannelTitle": "Google for Developers", + "videoOwnerChannelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw" + }, + "contentDetails": { + "videoId": "wysukDrMdqU", + "videoPublishedAt": "2023-05-11T00:20:46Z" + } + } + ], + "pageInfo": { + "totalResults": 5798, + "resultsPerPage": 1 + } +} diff --git a/tests/components/youtube/fixtures/thumbnail/medium.json b/tests/components/youtube/fixtures/thumbnail/medium.json new file mode 100644 index 00000000000..21cb09bd886 --- /dev/null +++ b/tests/components/youtube/fixtures/thumbnail/medium.json @@ -0,0 +1,47 @@ +{ + "kind": "youtube#playlistItemListResponse", + "etag": "O0Ah8Wd5pUD2Gsv-n0A42RDRcX8", + "nextPageToken": "EAAaBlBUOkNBVQ", + "items": [ + { + "kind": "youtube#playlistItem", + "etag": "qgpoAJRNskzLhD99njC8e2kPB0M", + "id": "VVVfeDVYRzFPVjJQNnVaWjVGU005VHR3Lnd5c3VrRHJNZHFV", + "snippet": { + "publishedAt": "2023-05-11T00:20:46Z", + "channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw", + "title": "What's new in Google Home in less than 1 minute", + "description": "Discover how your connected devices can do more with Google Home using Matter and Automations at Google I/O 2023.\n\nTo learn more about what's new in Google Home, check out the keynote → https://goo.gle/IO23_homekey\n\nSubscribe to Google Developers → https://goo.gle/developers \n\n#GoogleIO #GoogleHome", + "thumbnails": { + "default": { + "url": "https://i.ytimg.com/vi/wysukDrMdqU/default.jpg", + "width": 120, + "height": 90 + }, + "medium": { + "url": "https://i.ytimg.com/vi/wysukDrMdqU/mqdefault.jpg", + "width": 320, + "height": 180 + } + }, + "channelTitle": "Google for Developers", + "playlistId": "UU_x5XG1OV2P6uZZ5FSM9Ttw", + "position": 0, + "resourceId": { + "kind": "youtube#video", + "videoId": "wysukDrMdqU" + }, + "videoOwnerChannelTitle": "Google for Developers", + "videoOwnerChannelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw" + }, + "contentDetails": { + "videoId": "wysukDrMdqU", + "videoPublishedAt": "2023-05-11T00:20:46Z" + } + } + ], + "pageInfo": { + "totalResults": 5798, + "resultsPerPage": 1 + } +} diff --git a/tests/components/youtube/fixtures/thumbnail/none.json b/tests/components/youtube/fixtures/thumbnail/none.json new file mode 100644 index 00000000000..d4c28730cab --- /dev/null +++ b/tests/components/youtube/fixtures/thumbnail/none.json @@ -0,0 +1,36 @@ +{ + "kind": "youtube#playlistItemListResponse", + "etag": "O0Ah8Wd5pUD2Gsv-n0A42RDRcX8", + "nextPageToken": "EAAaBlBUOkNBVQ", + "items": [ + { + "kind": "youtube#playlistItem", + "etag": "qgpoAJRNskzLhD99njC8e2kPB0M", + "id": "VVVfeDVYRzFPVjJQNnVaWjVGU005VHR3Lnd5c3VrRHJNZHFV", + "snippet": { + "publishedAt": "2023-05-11T00:20:46Z", + "channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw", + "title": "What's new in Google Home in less than 1 minute", + "description": "Discover how your connected devices can do more with Google Home using Matter and Automations at Google I/O 2023.\n\nTo learn more about what's new in Google Home, check out the keynote → https://goo.gle/IO23_homekey\n\nSubscribe to Google Developers → https://goo.gle/developers \n\n#GoogleIO #GoogleHome", + "thumbnails": {}, + "channelTitle": "Google for Developers", + "playlistId": "UU_x5XG1OV2P6uZZ5FSM9Ttw", + "position": 0, + "resourceId": { + "kind": "youtube#video", + "videoId": "wysukDrMdqU" + }, + "videoOwnerChannelTitle": "Google for Developers", + "videoOwnerChannelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw" + }, + "contentDetails": { + "videoId": "wysukDrMdqU", + "videoPublishedAt": "2023-05-11T00:20:46Z" + } + } + ], + "pageInfo": { + "totalResults": 5798, + "resultsPerPage": 1 + } +} diff --git a/tests/components/youtube/fixtures/thumbnail/standard.json b/tests/components/youtube/fixtures/thumbnail/standard.json new file mode 100644 index 00000000000..bdbedfcf4c9 --- /dev/null +++ b/tests/components/youtube/fixtures/thumbnail/standard.json @@ -0,0 +1,57 @@ +{ + "kind": "youtube#playlistItemListResponse", + "etag": "O0Ah8Wd5pUD2Gsv-n0A42RDRcX8", + "nextPageToken": "EAAaBlBUOkNBVQ", + "items": [ + { + "kind": "youtube#playlistItem", + "etag": "qgpoAJRNskzLhD99njC8e2kPB0M", + "id": "VVVfeDVYRzFPVjJQNnVaWjVGU005VHR3Lnd5c3VrRHJNZHFV", + "snippet": { + "publishedAt": "2023-05-11T00:20:46Z", + "channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw", + "title": "What's new in Google Home in less than 1 minute", + "description": "Discover how your connected devices can do more with Google Home using Matter and Automations at Google I/O 2023.\n\nTo learn more about what's new in Google Home, check out the keynote → https://goo.gle/IO23_homekey\n\nSubscribe to Google Developers → https://goo.gle/developers \n\n#GoogleIO #GoogleHome", + "thumbnails": { + "default": { + "url": "https://i.ytimg.com/vi/wysukDrMdqU/default.jpg", + "width": 120, + "height": 90 + }, + "medium": { + "url": "https://i.ytimg.com/vi/wysukDrMdqU/mqdefault.jpg", + "width": 320, + "height": 180 + }, + "high": { + "url": "https://i.ytimg.com/vi/wysukDrMdqU/hqdefault.jpg", + "width": 480, + "height": 360 + }, + "standard": { + "url": "https://i.ytimg.com/vi/wysukDrMdqU/sddefault.jpg", + "width": 640, + "height": 480 + } + }, + "channelTitle": "Google for Developers", + "playlistId": "UU_x5XG1OV2P6uZZ5FSM9Ttw", + "position": 0, + "resourceId": { + "kind": "youtube#video", + "videoId": "wysukDrMdqU" + }, + "videoOwnerChannelTitle": "Google for Developers", + "videoOwnerChannelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw" + }, + "contentDetails": { + "videoId": "wysukDrMdqU", + "videoPublishedAt": "2023-05-11T00:20:46Z" + } + } + ], + "pageInfo": { + "totalResults": 5798, + "resultsPerPage": 1 + } +} diff --git a/tests/components/youtube/test_sensor.py b/tests/components/youtube/test_sensor.py index 3462e291af8..6bd99399952 100644 --- a/tests/components/youtube/test_sensor.py +++ b/tests/components/youtube/test_sensor.py @@ -3,6 +3,7 @@ from datetime import timedelta from unittest.mock import patch from google.auth.exceptions import RefreshError +import pytest from homeassistant import config_entries from homeassistant.components.youtube import DOMAIN @@ -87,3 +88,38 @@ async def test_sensor_reauth_trigger( assert flow["step_id"] == "reauth_confirm" assert flow["handler"] == DOMAIN assert flow["context"]["source"] == config_entries.SOURCE_REAUTH + + +@pytest.mark.parametrize( + ("fixture", "url", "has_entity_picture"), + [ + ("standard", "https://i.ytimg.com/vi/wysukDrMdqU/sddefault.jpg", True), + ("high", "https://i.ytimg.com/vi/wysukDrMdqU/hqdefault.jpg", True), + ("medium", "https://i.ytimg.com/vi/wysukDrMdqU/mqdefault.jpg", True), + ("default", "https://i.ytimg.com/vi/wysukDrMdqU/default.jpg", True), + ("none", None, False), + ], +) +async def test_thumbnail( + hass: HomeAssistant, + setup_integration: ComponentSetup, + fixture: str, + url: str | None, + has_entity_picture: bool, +) -> None: + """Test if right thumbnail is selected.""" + await setup_integration() + + with patch( + "homeassistant.components.youtube.api.build", + return_value=MockService( + playlist_items_fixture=f"youtube/thumbnail/{fixture}.json" + ), + ): + future = dt_util.utcnow() + timedelta(minutes=15) + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + state = hass.states.get("sensor.google_for_developers_latest_upload") + assert state + assert ("entity_picture" in state.attributes) is has_entity_picture + assert state.attributes.get("entity_picture") == url From 6329f6bc07684d4f6b7c7439139642c9edb78ef7 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 16 Jun 2023 09:10:55 +0200 Subject: [PATCH 65/83] Add strings for YouTube reauthentication (#94655) --- homeassistant/components/youtube/strings.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/homeassistant/components/youtube/strings.json b/homeassistant/components/youtube/strings.json index eb89738708e..1ecc2bc4db8 100644 --- a/homeassistant/components/youtube/strings.json +++ b/homeassistant/components/youtube/strings.json @@ -17,6 +17,10 @@ "data": { "channels": "YouTube channels" } + }, + "reauth_confirm": { + "title": "[%key:common::config_flow::title::reauth%]", + "description": "The YouTube integration needs to re-authenticate your account" } } }, From b52cfd33245ea31b029aedc9858d19e0c297938f Mon Sep 17 00:00:00 2001 From: Dirk Sarodnick Date: Fri, 16 Jun 2023 04:10:04 +0200 Subject: [PATCH 66/83] Fix bluetooth tracker asyncio usage (#94695) * fix for asyncio usage fixes the error "Passing coroutines is forbidden, use tasks explicitly", caused by passing an async function into asyncio.wait directly instead of creating a task for it. * removes unnecessary default param * corrects formatting for black --- .../components/bluetooth_tracker/device_tracker.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/bluetooth_tracker/device_tracker.py b/homeassistant/components/bluetooth_tracker/device_tracker.py index 659243df733..f4fc6a8df08 100644 --- a/homeassistant/components/bluetooth_tracker/device_tracker.py +++ b/homeassistant/components/bluetooth_tracker/device_tracker.py @@ -173,7 +173,11 @@ async def async_setup_scanner( rssi = await hass.async_add_executor_job(client.request_rssi) client.close() - tasks.append(see_device(hass, async_see, mac, friendly_name, rssi)) + tasks.append( + asyncio.create_task( + see_device(hass, async_see, mac, friendly_name, rssi) + ) + ) if tasks: await asyncio.wait(tasks) From 89c6494056c2c0710e8b51c946a6fc9267509270 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 16 Jun 2023 03:35:29 -0400 Subject: [PATCH 67/83] Fix zwave_js trigger event reattach logic (#94702) --- .../components/zwave_js/triggers/event.py | 7 +- .../zwave_js/triggers/value_updated.py | 4 +- tests/components/zwave_js/test_trigger.py | 118 +++++++++++++----- 3 files changed, 96 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/zwave_js/triggers/event.py b/homeassistant/components/zwave_js/triggers/event.py index 32bd3130e03..33cb59d8505 100644 --- a/homeassistant/components/zwave_js/triggers/event.py +++ b/homeassistant/components/zwave_js/triggers/event.py @@ -142,8 +142,9 @@ async def async_attach_trigger( ) -> CALLBACK_TYPE: """Listen for state changes based on configuration.""" dev_reg = dr.async_get(hass) - nodes = async_get_nodes_from_targets(hass, config, dev_reg=dev_reg) - if config[ATTR_EVENT_SOURCE] == "node" and not nodes: + if config[ATTR_EVENT_SOURCE] == "node" and not async_get_nodes_from_targets( + hass, config, dev_reg=dev_reg + ): raise ValueError( f"No nodes found for given {ATTR_DEVICE_ID}s or {ATTR_ENTITY_ID}s." ) @@ -215,7 +216,7 @@ async def async_attach_trigger( # Nodes list can come from different drivers and we will need to listen to # server connections for all of them. drivers: set[Driver] = set() - if not nodes: + if not (nodes := async_get_nodes_from_targets(hass, config, dev_reg=dev_reg)): entry_id = config[ATTR_CONFIG_ENTRY_ID] client: Client = hass.data[DOMAIN][entry_id][DATA_CLIENT] driver = client.driver diff --git a/homeassistant/components/zwave_js/triggers/value_updated.py b/homeassistant/components/zwave_js/triggers/value_updated.py index 4e21774c98f..52ecc0a7742 100644 --- a/homeassistant/components/zwave_js/triggers/value_updated.py +++ b/homeassistant/components/zwave_js/triggers/value_updated.py @@ -91,7 +91,7 @@ async def async_attach_trigger( ) -> CALLBACK_TYPE: """Listen for state changes based on configuration.""" dev_reg = dr.async_get(hass) - if not (nodes := async_get_nodes_from_targets(hass, config, dev_reg=dev_reg)): + if not async_get_nodes_from_targets(hass, config, dev_reg=dev_reg): raise ValueError( f"No nodes found for given {ATTR_DEVICE_ID}s or {ATTR_ENTITY_ID}s." ) @@ -174,7 +174,7 @@ async def async_attach_trigger( # Nodes list can come from different drivers and we will need to listen to # server connections for all of them. drivers: set[Driver] = set() - for node in nodes: + for node in async_get_nodes_from_targets(hass, config, dev_reg=dev_reg): driver = node.client.driver assert driver is not None # The node comes from the driver. drivers.add(driver) diff --git a/tests/components/zwave_js/test_trigger.py b/tests/components/zwave_js/test_trigger.py index 0fb3b829d9a..eae9d6f5416 100644 --- a/tests/components/zwave_js/test_trigger.py +++ b/tests/components/zwave_js/test_trigger.py @@ -1112,20 +1112,21 @@ def test_get_trigger_platform_failure() -> None: async def test_server_reconnect_event( - hass: HomeAssistant, client, lock_schlage_be469, integration + hass: HomeAssistant, + client, + lock_schlage_be469, + lock_schlage_be469_state, + integration, ) -> None: """Test that when we reconnect to server, event triggers reattach.""" trigger_type = f"{DOMAIN}.event" - node: Node = lock_schlage_be469 - dev_reg = async_get_dev_reg(hass) - device = dev_reg.async_get_device( - {get_device_id(client.driver, lock_schlage_be469)} - ) - assert device + old_node: Node = lock_schlage_be469 event_name = "interview stage completed" - original_len = len(node._listeners.get(event_name, [])) + old_node = client.driver.controller.nodes[20] + + original_len = len(old_node._listeners.get(event_name, [])) assert await async_setup_component( hass, @@ -1147,34 +1148,65 @@ async def test_server_reconnect_event( }, ) - assert len(node._listeners.get(event_name, [])) == original_len + 1 - old_listener = node._listeners.get(event_name, [])[original_len] + assert len(old_node._listeners.get(event_name, [])) == original_len + 1 + old_listener = old_node._listeners.get(event_name, [])[original_len] + # Remove node so that we can create a new node instance and make sure the listener + # attaches + node_removed_event = Event( + type="node removed", + data={ + "source": "controller", + "event": "node removed", + "replaced": False, + "node": lock_schlage_be469_state, + }, + ) + client.driver.controller.receive_event(node_removed_event) + assert 20 not in client.driver.controller.nodes + await hass.async_block_till_done() + + # Add node like new server connection would + node_added_event = Event( + type="node added", + data={ + "source": "controller", + "event": "node added", + "node": lock_schlage_be469_state, + "result": {}, + }, + ) + client.driver.controller.receive_event(node_added_event) + await hass.async_block_till_done() + + # Reload integration to trigger the dispatch signal await hass.config_entries.async_reload(integration.entry_id) await hass.async_block_till_done() - # Make sure there is still a listener added for the trigger - assert len(node._listeners.get(event_name, [])) == original_len + 1 + # Make sure there is a listener added for the trigger to the new node + new_node = client.driver.controller.nodes[20] + assert len(new_node._listeners.get(event_name, [])) == original_len + 1 - # Make sure the old listener was removed - assert old_listener not in node._listeners.get(event_name, []) + # Make sure the old listener is no longer referenced + assert old_listener not in new_node._listeners.get(event_name, []) async def test_server_reconnect_value_updated( - hass: HomeAssistant, client, lock_schlage_be469, integration + hass: HomeAssistant, + client, + lock_schlage_be469, + lock_schlage_be469_state, + integration, ) -> None: """Test that when we reconnect to server, value_updated triggers reattach.""" trigger_type = f"{DOMAIN}.value_updated" - node: Node = lock_schlage_be469 - dev_reg = async_get_dev_reg(hass) - device = dev_reg.async_get_device( - {get_device_id(client.driver, lock_schlage_be469)} - ) - assert device + old_node: Node = lock_schlage_be469 event_name = "value updated" - original_len = len(node._listeners.get(event_name, [])) + old_node = client.driver.controller.nodes[20] + + original_len = len(old_node._listeners.get(event_name, [])) assert await async_setup_component( hass, @@ -1196,14 +1228,44 @@ async def test_server_reconnect_value_updated( }, ) - assert len(node._listeners.get(event_name, [])) == original_len + 1 - old_listener = node._listeners.get(event_name, [])[original_len] + assert len(old_node._listeners.get(event_name, [])) == original_len + 1 + old_listener = old_node._listeners.get(event_name, [])[original_len] + # Remove node so that we can create a new node instance and make sure the listener + # attaches + node_removed_event = Event( + type="node removed", + data={ + "source": "controller", + "event": "node removed", + "replaced": False, + "node": lock_schlage_be469_state, + }, + ) + client.driver.controller.receive_event(node_removed_event) + assert 20 not in client.driver.controller.nodes + await hass.async_block_till_done() + + # Add node like new server connection would + node_added_event = Event( + type="node added", + data={ + "source": "controller", + "event": "node added", + "node": lock_schlage_be469_state, + "result": {}, + }, + ) + client.driver.controller.receive_event(node_added_event) + await hass.async_block_till_done() + + # Reload integration to trigger the dispatch signal await hass.config_entries.async_reload(integration.entry_id) await hass.async_block_till_done() - # Make sure there is still a listener added for the trigger - assert len(node._listeners.get(event_name, [])) == original_len + 1 + # Make sure there is a listener added for the trigger to the new node + new_node = client.driver.controller.nodes[20] + assert len(new_node._listeners.get(event_name, [])) == original_len + 1 - # Make sure the old listener was removed - assert old_listener not in node._listeners.get(event_name, []) + # Make sure the old listener is no longer referenced + assert old_listener not in new_node._listeners.get(event_name, []) From 254b1fd3148e6f2490eea4ecc1c5f7568fd07424 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Fri, 16 Jun 2023 19:12:52 +0200 Subject: [PATCH 68/83] Fix warning from rapt_ble caused by payload version 2 (#94718) --- homeassistant/components/rapt_ble/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/rapt_ble/manifest.json b/homeassistant/components/rapt_ble/manifest.json index d3eab0641a6..1bde135de35 100644 --- a/homeassistant/components/rapt_ble/manifest.json +++ b/homeassistant/components/rapt_ble/manifest.json @@ -16,5 +16,5 @@ "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/rapt_ble", "iot_class": "local_push", - "requirements": ["rapt-ble==0.1.1"] + "requirements": ["rapt-ble==0.1.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 7e62831c75e..49db0fce4be 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2240,7 +2240,7 @@ radiotherm==2.1.0 raincloudy==0.0.7 # homeassistant.components.rapt_ble -rapt-ble==0.1.1 +rapt-ble==0.1.2 # homeassistant.components.raspyrfm raspyrfm-client==1.2.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bb5fa31f9fa..4c5ae788b41 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1627,7 +1627,7 @@ radios==0.1.1 radiotherm==2.1.0 # homeassistant.components.rapt_ble -rapt-ble==0.1.1 +rapt-ble==0.1.2 # homeassistant.components.rainmachine regenmaschine==2023.06.0 From f33d671a5d887cc3a95cae53dde709d28dcd5c31 Mon Sep 17 00:00:00 2001 From: Austin Mroczek Date: Tue, 20 Jun 2023 13:12:11 -0700 Subject: [PATCH 69/83] Fix Totalconnect BinarySensorDeviceClass logic (#94772) * handle temperature * test for temperature * test for unknown --- .../components/totalconnect/binary_sensor.py | 12 +++--------- tests/components/totalconnect/common.py | 13 +++++++++++-- .../totalconnect/test_binary_sensor.py | 18 ++++++++++++++++++ 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/totalconnect/binary_sensor.py b/homeassistant/components/totalconnect/binary_sensor.py index ef252d54e4e..9caa642b5f4 100644 --- a/homeassistant/components/totalconnect/binary_sensor.py +++ b/homeassistant/components/totalconnect/binary_sensor.py @@ -81,15 +81,9 @@ class TotalConnectZoneSecurityBinarySensor(TotalConnectZoneBinarySensor): return BinarySensorDeviceClass.MOTION if self._zone.is_type_medical(): return BinarySensorDeviceClass.SAFETY - # "security" type is a generic category so test for it last - if self._zone.is_type_security(): - return BinarySensorDeviceClass.DOOR - - _LOGGER.error( - "TotalConnect zone %s reported an unexpected device class", - self._zone.zoneid, - ) - return None + if self._zone.is_type_temperature(): + return BinarySensorDeviceClass.PROBLEM + return BinarySensorDeviceClass.DOOR def update(self): """Return the state of the device.""" diff --git a/tests/components/totalconnect/common.py b/tests/components/totalconnect/common.py index 54f321c6770..ccee4c43781 100644 --- a/tests/components/totalconnect/common.py +++ b/tests/components/totalconnect/common.py @@ -189,14 +189,23 @@ ZONE_5 = { # 99 is an unknown ZoneType ZONE_6 = { "ZoneID": "6", - "ZoneDescription": "Medical", + "ZoneDescription": "Unknown", "ZoneStatus": ZoneStatus.NORMAL, "ZoneTypeId": 99, "PartitionId": "1", "CanBeBypassed": 0, } -ZONE_INFO = [ZONE_NORMAL, ZONE_2, ZONE_3, ZONE_4, ZONE_5, ZONE_6] +ZONE_7 = { + "ZoneID": 7, + "ZoneDescription": "Temperature", + "ZoneStatus": ZoneStatus.NORMAL, + "ZoneTypeId": ZoneType.MONITOR, + "PartitionId": "1", + "CanBeBypassed": 0, +} + +ZONE_INFO = [ZONE_NORMAL, ZONE_2, ZONE_3, ZONE_4, ZONE_5, ZONE_6, ZONE_7] ZONES = {"ZoneInfo": ZONE_INFO} METADATA_DISARMED = { diff --git a/tests/components/totalconnect/test_binary_sensor.py b/tests/components/totalconnect/test_binary_sensor.py index 966daeb5a63..8f9cabe670c 100644 --- a/tests/components/totalconnect/test_binary_sensor.py +++ b/tests/components/totalconnect/test_binary_sensor.py @@ -84,3 +84,21 @@ async def test_state_and_attributes(hass: HomeAssistant) -> None: assert state.state == STATE_OFF state = hass.states.get("binary_sensor.gas_tamper") assert state.state == STATE_ON + + # Zone 6 is unknown type, assume it is a security (door) sensor + state = hass.states.get("binary_sensor.unknown") + assert state.state == STATE_OFF + assert state.attributes.get("device_class") == BinarySensorDeviceClass.DOOR + state = hass.states.get("binary_sensor.unknown_low_battery") + assert state.state == STATE_OFF + state = hass.states.get("binary_sensor.unknown_tamper") + assert state.state == STATE_OFF + + # Zone 7 is temperature + state = hass.states.get("binary_sensor.temperature") + assert state.state == STATE_OFF + assert state.attributes.get("device_class") == BinarySensorDeviceClass.PROBLEM + state = hass.states.get("binary_sensor.temperature_low_battery") + assert state.state == STATE_OFF + state = hass.states.get("binary_sensor.temperature_tamper") + assert state.state == STATE_OFF From e1751647f4a21cabdc7a9327a2248c4e68e7e6d3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 20 Jun 2023 22:06:26 +0100 Subject: [PATCH 70/83] Bump HAP-python to 4.7.0 (#94812) --- .../components/homekit/accessories.py | 4 ++-- .../components/homekit/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/homekit/test_homekit.py | 21 +++++++++++-------- 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index dc8a2a7c639..a2e3f8487c6 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -626,10 +626,10 @@ class HomeDriver(AccessoryDriver): # type: ignore[misc] @pyhap_callback # type: ignore[misc] def pair( - self, client_uuid: UUID, client_public: str, client_permissions: int + self, client_username_bytes: bytes, client_public: str, client_permissions: int ) -> bool: """Override super function to dismiss setup message if paired.""" - success = super().pair(client_uuid, client_public, client_permissions) + success = super().pair(client_username_bytes, client_public, client_permissions) if success: async_dismiss_setup_message(self.hass, self._entry_id) return cast(bool, success) diff --git a/homeassistant/components/homekit/manifest.json b/homeassistant/components/homekit/manifest.json index 746b097e99a..245dbd0a19e 100644 --- a/homeassistant/components/homekit/manifest.json +++ b/homeassistant/components/homekit/manifest.json @@ -9,7 +9,7 @@ "iot_class": "local_push", "loggers": ["pyhap"], "requirements": [ - "HAP-python==4.6.0", + "HAP-python==4.7.0", "fnv-hash-fast==0.3.1", "PyQRCode==1.2.1", "base36==0.1.1" diff --git a/requirements_all.txt b/requirements_all.txt index 49db0fce4be..3ed442f2369 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -11,7 +11,7 @@ AIOAladdinConnect==0.1.56 Adax-local==0.1.5 # homeassistant.components.homekit -HAP-python==4.6.0 +HAP-python==4.7.0 # homeassistant.components.mastodon Mastodon.py==1.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4c5ae788b41..ece221b109c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -13,7 +13,7 @@ AIOAladdinConnect==0.1.56 Adax-local==0.1.5 # homeassistant.components.homekit -HAP-python==4.6.0 +HAP-python==4.7.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 0b74763c6a7..0ffa7893fbd 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -3,6 +3,7 @@ from __future__ import annotations import asyncio from unittest.mock import ANY, AsyncMock, MagicMock, Mock, patch +from uuid import uuid1 from pyhap.accessory import Accessory from pyhap.const import CATEGORY_CAMERA, CATEGORY_TELEVISION @@ -868,11 +869,11 @@ async def test_homekit_unpair( homekit.driver.aio_stop_event = MagicMock() state = homekit.driver.state - state.add_paired_client("client1", "any", b"1") - state.add_paired_client("client2", "any", b"0") - state.add_paired_client("client3", "any", b"1") - state.add_paired_client("client4", "any", b"0") - state.add_paired_client("client5", "any", b"0") + state.add_paired_client(str(uuid1()).encode("utf-8"), "any", b"1") + state.add_paired_client(str(uuid1()).encode("utf-8"), "any", b"0") + state.add_paired_client(str(uuid1()).encode("utf-8"), "any", b"1") + state.add_paired_client(str(uuid1()).encode("utf-8"), "any", b"0") + state.add_paired_client(str(uuid1()).encode("utf-8"), "any", b"0") formatted_mac = dr.format_mac(state.mac) hk_bridge_dev = device_registry.async_get_device( @@ -917,7 +918,8 @@ async def test_homekit_unpair_missing_device_id( homekit.driver.aio_stop_event = MagicMock() state = homekit.driver.state - state.add_paired_client("client1", "any", b"1") + client_1 = str(uuid1()).encode("utf-8") + state.add_paired_client(client_1, "any", b"1") with pytest.raises(HomeAssistantError): await hass.services.async_call( DOMAIN, @@ -926,7 +928,7 @@ async def test_homekit_unpair_missing_device_id( blocking=True, ) await hass.async_block_till_done() - state.paired_clients = {"client1": "any"} + state.paired_clients = {client_1.decode("utf-8"): "any"} homekit.status = STATUS_STOPPED @@ -967,7 +969,8 @@ async def test_homekit_unpair_not_homekit_device( ) state = homekit.driver.state - state.add_paired_client("client1", "any", b"1") + client_1 = str(uuid1()).encode("utf-8") + state.add_paired_client(client_1, "any", b"1") with pytest.raises(HomeAssistantError): await hass.services.async_call( DOMAIN, @@ -976,7 +979,7 @@ async def test_homekit_unpair_not_homekit_device( blocking=True, ) await hass.async_block_till_done() - state.paired_clients = {"client1": "any"} + state.paired_clients = {client_1.decode("utf-8"): "any"} homekit.status = STATUS_STOPPED From cee8004641fcd83729f947f55847fc4c0a8d4727 Mon Sep 17 00:00:00 2001 From: Ernst Klamer Date: Sun, 18 Jun 2023 22:06:09 +0200 Subject: [PATCH 71/83] Bump bthome to 2.12.0 (#94822) --- homeassistant/components/bthome/manifest.json | 2 +- homeassistant/components/bthome/sensor.py | 27 ++++++++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/bthome/test_sensor.py | 51 +++++++++++++++++++ 5 files changed, 81 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/bthome/manifest.json b/homeassistant/components/bthome/manifest.json index ef3d9bc002d..91f4940a4e5 100644 --- a/homeassistant/components/bthome/manifest.json +++ b/homeassistant/components/bthome/manifest.json @@ -20,5 +20,5 @@ "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/bthome", "iot_class": "local_push", - "requirements": ["bthome-ble==2.11.3"] + "requirements": ["bthome-ble==2.12.0"] } diff --git a/homeassistant/components/bthome/sensor.py b/homeassistant/components/bthome/sensor.py index f8693c5fb34..fc8673e801b 100644 --- a/homeassistant/components/bthome/sensor.py +++ b/homeassistant/components/bthome/sensor.py @@ -47,6 +47,15 @@ from .coordinator import ( from .device import device_key_to_bluetooth_entity_key SENSOR_DESCRIPTIONS = { + # Acceleration (m/s²) + ( + BTHomeSensorDeviceClass.ACCELERATION, + Units.ACCELERATION_METERS_PER_SQUARE_SECOND, + ): SensorEntityDescription( + key=f"{BTHomeSensorDeviceClass.ACCELERATION}_{Units.ACCELERATION_METERS_PER_SQUARE_SECOND}", + native_unit_of_measurement=Units.ACCELERATION_METERS_PER_SQUARE_SECOND, + state_class=SensorStateClass.MEASUREMENT, + ), # Battery (percent) (BTHomeSensorDeviceClass.BATTERY, Units.PERCENTAGE): SensorEntityDescription( key=f"{BTHomeSensorDeviceClass.BATTERY}_{Units.PERCENTAGE}", @@ -131,6 +140,15 @@ SENSOR_DESCRIPTIONS = { native_unit_of_measurement=UnitOfVolume.CUBIC_METERS, state_class=SensorStateClass.TOTAL, ), + # Gyroscope (°/s) + ( + BTHomeSensorDeviceClass.GYROSCOPE, + Units.GYROSCOPE_DEGREES_PER_SECOND, + ): SensorEntityDescription( + key=f"{BTHomeSensorDeviceClass.GYROSCOPE}_{Units.GYROSCOPE_DEGREES_PER_SECOND}", + native_unit_of_measurement=Units.GYROSCOPE_DEGREES_PER_SECOND, + state_class=SensorStateClass.MEASUREMENT, + ), # Humidity in (percent) (BTHomeSensorDeviceClass.HUMIDITY, Units.PERCENTAGE): SensorEntityDescription( key=f"{BTHomeSensorDeviceClass.HUMIDITY}_{Units.PERCENTAGE}", @@ -242,6 +260,15 @@ SENSOR_DESCRIPTIONS = { native_unit_of_measurement=UnitOfTemperature.CELSIUS, state_class=SensorStateClass.MEASUREMENT, ), + # Timestamp (datetime object) + ( + BTHomeSensorDeviceClass.TIMESTAMP, + None, + ): SensorEntityDescription( + key=f"{BTHomeSensorDeviceClass.TIMESTAMP}", + device_class=SensorDeviceClass.TIMESTAMP, + state_class=SensorStateClass.MEASUREMENT, + ), # UV index (-) ( BTHomeSensorDeviceClass.UV_INDEX, diff --git a/requirements_all.txt b/requirements_all.txt index 3ed442f2369..61f42e8ec61 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -502,7 +502,7 @@ brunt==1.2.0 bt_proximity==0.2.1 # homeassistant.components.bthome -bthome-ble==2.11.3 +bthome-ble==2.12.0 # homeassistant.components.bt_home_hub_5 bthomehub5-devicelist==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ece221b109c..e2303be27c8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -415,7 +415,7 @@ brottsplatskartan==0.0.1 brunt==1.2.0 # homeassistant.components.bthome -bthome-ble==2.11.3 +bthome-ble==2.12.0 # homeassistant.components.buienradar buienradar==1.0.5 diff --git a/tests/components/bthome/test_sensor.py b/tests/components/bthome/test_sensor.py index 7aafe7ba7a9..4450bfcc936 100644 --- a/tests/components/bthome/test_sensor.py +++ b/tests/components/bthome/test_sensor.py @@ -858,6 +858,57 @@ async def test_v1_sensors( }, ], ), + ( + "A4:C1:38:8D:18:B2", + make_bthome_v2_adv( + "A4:C1:38:8D:18:B2", + b"\x44\x50\x5D\x39\x61\x64", + ), + None, + [ + { + "sensor_entity": "sensor.test_device_18b2_timestamp", + "friendly_name": "Test Device 18B2 Timestamp", + "unit_of_measurement": "s", + "state_class": "measurement", + "expected_state": "2023-05-14T19:41:17+00:00", + }, + ], + ), + ( + "A4:C1:38:8D:18:B2", + make_bthome_v2_adv( + "A4:C1:38:8D:18:B2", + b"\x44\x51\x87\x56", + ), + None, + [ + { + "sensor_entity": "sensor.test_device_18b2_acceleration", + "friendly_name": "Test Device 18B2 Acceleration", + "unit_of_measurement": "m/s²", + "state_class": "measurement", + "expected_state": "22.151", + }, + ], + ), + ( + "A4:C1:38:8D:18:B2", + make_bthome_v2_adv( + "A4:C1:38:8D:18:B2", + b"\x44\x52\x87\x56", + ), + None, + [ + { + "sensor_entity": "sensor.test_device_18b2_gyroscope", + "friendly_name": "Test Device 18B2 Gyroscope", + "unit_of_measurement": "°/s", + "state_class": "measurement", + "expected_state": "22.151", + }, + ], + ), ( "A4:C1:38:8D:18:B2", make_bthome_v2_adv( From 5da5522481f1434c10e11ef9201cc36b82764e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Tue, 20 Jun 2023 02:23:24 +0200 Subject: [PATCH 72/83] Update aioairzone to v0.6.4 (#94873) --- homeassistant/components/airzone/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/airzone/manifest.json b/homeassistant/components/airzone/manifest.json index 637066629db..88b918f699c 100644 --- a/homeassistant/components/airzone/manifest.json +++ b/homeassistant/components/airzone/manifest.json @@ -11,5 +11,5 @@ "documentation": "https://www.home-assistant.io/integrations/airzone", "iot_class": "local_polling", "loggers": ["aioairzone"], - "requirements": ["aioairzone==0.6.3"] + "requirements": ["aioairzone==0.6.4"] } diff --git a/requirements_all.txt b/requirements_all.txt index 61f42e8ec61..c5316e6bc82 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -119,7 +119,7 @@ aioairq==0.2.4 aioairzone-cloud==0.1.8 # homeassistant.components.airzone -aioairzone==0.6.3 +aioairzone==0.6.4 # homeassistant.components.ambient_station aioambient==2023.04.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e2303be27c8..abdad0c7325 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -109,7 +109,7 @@ aioairq==0.2.4 aioairzone-cloud==0.1.8 # homeassistant.components.airzone -aioairzone==0.6.3 +aioairzone==0.6.4 # homeassistant.components.ambient_station aioambient==2023.04.0 From f8cfaa61470900464e4e2bee009cf2e42eb04ee9 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Tue, 20 Jun 2023 07:32:03 -0700 Subject: [PATCH 73/83] Bump ical to 4.5.4 (#94894) --- homeassistant/components/local_calendar/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/local_calendar/manifest.json b/homeassistant/components/local_calendar/manifest.json index 049f9de03ea..b56acffe4e2 100644 --- a/homeassistant/components/local_calendar/manifest.json +++ b/homeassistant/components/local_calendar/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/local_calendar", "iot_class": "local_polling", "loggers": ["ical"], - "requirements": ["ical==4.5.1"] + "requirements": ["ical==4.5.4"] } diff --git a/requirements_all.txt b/requirements_all.txt index c5316e6bc82..8ff40d3a70e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -966,7 +966,7 @@ ibeacon_ble==1.0.1 ibmiotf==0.3.4 # homeassistant.components.local_calendar -ical==4.5.1 +ical==4.5.4 # homeassistant.components.ping icmplib==3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index abdad0c7325..56a11b87c4c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -746,7 +746,7 @@ iaqualink==0.5.0 ibeacon_ble==1.0.1 # homeassistant.components.local_calendar -ical==4.5.1 +ical==4.5.4 # homeassistant.components.ping icmplib==3.0 From 34ef89b16b5e0fa629705b176eabacc7bbbb7bb5 Mon Sep 17 00:00:00 2001 From: Quentame Date: Thu, 22 Jun 2023 15:59:48 +0200 Subject: [PATCH 74/83] Fix Meteo France blocked config entry when weather alert API fails (#94911) * Fix: do not block config entry when weather alert API fails * PR review --- homeassistant/components/meteo_france/__init__.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/meteo_france/__init__.py b/homeassistant/components/meteo_france/__init__.py index 3b82399f217..ccd23762850 100644 --- a/homeassistant/components/meteo_france/__init__.py +++ b/homeassistant/components/meteo_france/__init__.py @@ -133,10 +133,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await coordinator_alert.async_refresh() - if not coordinator_alert.last_update_success: - raise ConfigEntryNotReady - - hass.data[DOMAIN][department] = True + if coordinator_alert.last_update_success: + hass.data[DOMAIN][department] = True else: _LOGGER.warning( ( @@ -158,11 +156,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: undo_listener = entry.add_update_listener(_async_update_listener) hass.data[DOMAIN][entry.entry_id] = { + UNDO_UPDATE_LISTENER: undo_listener, COORDINATOR_FORECAST: coordinator_forecast, COORDINATOR_RAIN: coordinator_rain, - COORDINATOR_ALERT: coordinator_alert, - UNDO_UPDATE_LISTENER: undo_listener, } + if coordinator_alert and coordinator_alert.last_update_success: + hass.data[DOMAIN][entry.entry_id][COORDINATOR_ALERT] = coordinator_alert await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) From cda784c96963e4d8a3c9b53816ee28eb3a636add Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Wed, 21 Jun 2023 11:17:11 +0200 Subject: [PATCH 75/83] Add error handling to hassio issues (#94951) --- homeassistant/components/hassio/issues.py | 6 +++++- tests/components/hassio/test_issues.py | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/hassio/issues.py b/homeassistant/components/hassio/issues.py index a92fc392fa4..8f7f06a3931 100644 --- a/homeassistant/components/hassio/issues.py +++ b/homeassistant/components/hassio/issues.py @@ -305,7 +305,11 @@ class SupervisorIssues: async def update(self) -> None: """Update issues from Supervisor resolution center.""" - data = await self._client.get_resolution_info() + try: + data = await self._client.get_resolution_info() + except HassioAPIError as err: + _LOGGER.error("Failed to update supervisor issues: %r", err) + return self.unhealthy_reasons = set(data[ATTR_UNHEALTHY]) self.unsupported_reasons = set(data[ATTR_UNSUPPORTED]) diff --git a/tests/components/hassio/test_issues.py b/tests/components/hassio/test_issues.py index 7bd30e452c0..4d694b79e46 100644 --- a/tests/components/hassio/test_issues.py +++ b/tests/components/hassio/test_issues.py @@ -715,3 +715,21 @@ async def test_supervisor_remove_missing_issue_without_error( msg = await client.receive_json() assert msg["success"] await hass.async_block_till_done() + + +async def test_system_is_not_ready( + hass: HomeAssistant, + aioclient_mock: AiohttpClientMocker, + caplog: pytest.LogCaptureFixture, +) -> None: + """Ensure hassio starts despite error.""" + aioclient_mock.get( + "http://127.0.0.1/resolution/info", + json={ + "result": "", + "message": "System is not ready with state: setup", + }, + ) + + assert await async_setup_component(hass, "hassio", {}) + assert "Failed to update supervisor issues" in caplog.text From bbbc9f646ffe10fe7f07db306d84de62764e934c Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Wed, 21 Jun 2023 23:57:33 +0200 Subject: [PATCH 76/83] Bump Matter Server to 3.5.1: some small fixes and stability improvements (#94985) --- homeassistant/components/matter/adapter.py | 4 ++-- homeassistant/components/matter/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/matter/adapter.py b/homeassistant/components/matter/adapter.py index 7d73ceafc7a..9f16dae8334 100644 --- a/homeassistant/components/matter/adapter.py +++ b/homeassistant/components/matter/adapter.py @@ -92,7 +92,7 @@ class MatterAdapter: get_clean_name(basic_info.nodeLabel) or get_clean_name(basic_info.productLabel) or get_clean_name(basic_info.productName) - or device_type.__class__.__name__ + or device_type.__name__ if device_type else None ) @@ -117,7 +117,7 @@ class MatterAdapter: identifiers.add((DOMAIN, f"{ID_TYPE_SERIAL}_{basic_info.serialNumber}")) model = ( - get_clean_name(basic_info.productName) or device_type.__class__.__name__ + get_clean_name(basic_info.productName) or device_type.__name__ if device_type else None ) diff --git a/homeassistant/components/matter/manifest.json b/homeassistant/components/matter/manifest.json index 5af01f2eea5..707f7e70ee3 100644 --- a/homeassistant/components/matter/manifest.json +++ b/homeassistant/components/matter/manifest.json @@ -6,5 +6,5 @@ "dependencies": ["websocket_api"], "documentation": "https://www.home-assistant.io/integrations/matter", "iot_class": "local_push", - "requirements": ["python-matter-server==3.4.1"] + "requirements": ["python-matter-server==3.5.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 8ff40d3a70e..2adc8445293 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2087,7 +2087,7 @@ python-kasa==0.5.1 # python-lirc==1.2.3 # homeassistant.components.matter -python-matter-server==3.4.1 +python-matter-server==3.5.1 # homeassistant.components.xiaomi_miio python-miio==0.5.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 56a11b87c4c..f470ffb31ee 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1522,7 +1522,7 @@ python-juicenet==1.1.0 python-kasa==0.5.1 # homeassistant.components.matter -python-matter-server==3.4.1 +python-matter-server==3.5.1 # homeassistant.components.xiaomi_miio python-miio==0.5.12 From 44e7243e254758bb5bfbe4a15adc39c6125e2bb1 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 22 Jun 2023 10:10:36 -0400 Subject: [PATCH 77/83] Fix zwave_js device diagnostics dump (#94999) * Fix zwave_js device diagnostics dump * Update tests/components/zwave_js/test_diagnostics.py Co-authored-by: Martin Hjelmare * Update tests/components/zwave_js/test_diagnostics.py Co-authored-by: Martin Hjelmare * Update tests/components/zwave_js/test_diagnostics.py Co-authored-by: Martin Hjelmare * Update tests/components/zwave_js/test_diagnostics.py Co-authored-by: Martin Hjelmare * Update tests/components/zwave_js/test_diagnostics.py Co-authored-by: Martin Hjelmare * Update tests/components/zwave_js/test_diagnostics.py Co-authored-by: Martin Hjelmare * improve test --------- Co-authored-by: Martin Hjelmare --- .../components/zwave_js/diagnostics.py | 8 +++- tests/components/zwave_js/test_diagnostics.py | 43 +++++++++++++++---- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/zwave_js/diagnostics.py b/homeassistant/components/zwave_js/diagnostics.py index 4f52c41a085..2fe2b17fe1b 100644 --- a/homeassistant/components/zwave_js/diagnostics.py +++ b/homeassistant/components/zwave_js/diagnostics.py @@ -65,7 +65,7 @@ def redact_node_state(node_state: NodeDataType) -> NodeDataType: def get_device_entities( - hass: HomeAssistant, node: Node, device: dr.DeviceEntry + hass: HomeAssistant, node: Node, config_entry: ConfigEntry, device: dr.DeviceEntry ) -> list[dict[str, Any]]: """Get entities for a device.""" entity_entries = er.async_entries_for_device( @@ -73,6 +73,10 @@ def get_device_entities( ) entities = [] for entry in entity_entries: + # Skip entities that are not part of this integration + if entry.config_entry_id != config_entry.entry_id: + continue + # If the value ID returns as None, we don't need to include this entity if (value_id := get_value_id_from_unique_id(entry.unique_id)) is None: continue @@ -142,7 +146,7 @@ async def async_get_device_diagnostics( if node_id is None or node_id not in driver.controller.nodes: raise ValueError(f"Node for device {device.id} can't be found") node = driver.controller.nodes[node_id] - entities = get_device_entities(hass, node, device) + entities = get_device_entities(hass, node, config_entry, device) assert client.version node_state = redact_node_state(async_redact_data(node.data, KEYS_TO_REDACT)) return { diff --git a/tests/components/zwave_js/test_diagnostics.py b/tests/components/zwave_js/test_diagnostics.py index e7d7d9594bd..aa5ec77b798 100644 --- a/tests/components/zwave_js/test_diagnostics.py +++ b/tests/components/zwave_js/test_diagnostics.py @@ -18,11 +18,11 @@ from homeassistant.components.zwave_js.helpers import ( get_value_id_from_unique_id, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers.device_registry import async_get as async_get_dev_reg -from homeassistant.helpers.entity_registry import async_get as async_get_ent_reg +from homeassistant.helpers import device_registry as dr, entity_registry as er from .common import PROPERTY_ULTRAVIOLET +from tests.common import MockConfigEntry from tests.components.diagnostics import ( get_diagnostics_for_config_entry, get_diagnostics_for_device, @@ -57,10 +57,26 @@ async def test_device_diagnostics( version_state, ) -> None: """Test the device level diagnostics data dump.""" - dev_reg = async_get_dev_reg(hass) + dev_reg = dr.async_get(hass) device = dev_reg.async_get_device({get_device_id(client.driver, multisensor_6)}) assert device + # Create mock config entry for fake entity + mock_config_entry = MockConfigEntry(domain="test_integration") + mock_config_entry.add_to_hass(hass) + + # Add an entity entry to the device that is not part of this config entry + ent_reg = er.async_get(hass) + ent_reg.async_get_or_create( + "test", + "test_integration", + "test_unique_id", + suggested_object_id="unrelated_entity", + config_entry=mock_config_entry, + device_id=device.id, + ) + assert ent_reg.async_get("test.unrelated_entity") + # Update a value and ensure it is reflected in the node state event = Event( type="value updated", @@ -92,16 +108,27 @@ async def test_device_diagnostics( } # Assert that we only have the entities that were discovered for this device # Entities that are created outside of discovery (e.g. node status sensor and - # ping button) should not be in dump. + # ping button) as well as helper entities created from other integrations should + # not be in dump. assert len(diagnostics_data["entities"]) == len( list(async_discover_node_values(multisensor_6, device, {device.id: set()})) ) + assert any( + entity.entity_id == "test.unrelated_entity" + for entity in er.async_entries_for_device(ent_reg, device.id) + ) + # Explicitly check that the entity that is not part of this config entry is not + # in the dump. + assert not any( + entity["entity_id"] == "test.unrelated_entity" + for entity in diagnostics_data["entities"] + ) assert diagnostics_data["state"] == multisensor_6.data async def test_device_diagnostics_error(hass: HomeAssistant, integration) -> None: """Test the device diagnostics raises exception when an invalid device is used.""" - dev_reg = async_get_dev_reg(hass) + dev_reg = dr.async_get(hass) device = dev_reg.async_get_or_create( config_entry_id=integration.entry_id, identifiers={("test", "test")} ) @@ -123,12 +150,12 @@ async def test_device_diagnostics_missing_primary_value( hass_client: ClientSessionGenerator, ) -> None: """Test that device diagnostics handles an entity with a missing primary value.""" - dev_reg = async_get_dev_reg(hass) + dev_reg = dr.async_get(hass) device = dev_reg.async_get_device({get_device_id(client.driver, multisensor_6)}) assert device entity_id = "sensor.multisensor_6_air_temperature" - ent_reg = async_get_ent_reg(hass) + ent_reg = er.async_get(hass) entry = ent_reg.async_get(entity_id) # check that the primary value for the entity exists in the diagnostics @@ -212,7 +239,7 @@ async def test_device_diagnostics_secret_value( client.driver.controller.nodes[node.node_id] = node client.driver.controller.emit("node added", {"node": node}) await hass.async_block_till_done() - dev_reg = async_get_dev_reg(hass) + dev_reg = dr.async_get(hass) device = dev_reg.async_get_device({get_device_id(client.driver, node)}) assert device From ced6968e8598f6c983dd0de4d25c25eda894ec4e Mon Sep 17 00:00:00 2001 From: Richard Kroegel <42204099+rikroe@users.noreply.github.com> Date: Thu, 22 Jun 2023 00:17:13 +0200 Subject: [PATCH 78/83] Bump bimmer_connected to 0.13.7 (#95017) --- homeassistant/components/bmw_connected_drive/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/bmw_connected_drive/manifest.json b/homeassistant/components/bmw_connected_drive/manifest.json index c9612d00c64..d30198bdc12 100644 --- a/homeassistant/components/bmw_connected_drive/manifest.json +++ b/homeassistant/components/bmw_connected_drive/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive", "iot_class": "cloud_polling", "loggers": ["bimmer_connected"], - "requirements": ["bimmer_connected==0.13.6"] + "requirements": ["bimmer-connected==0.13.7"] } diff --git a/requirements_all.txt b/requirements_all.txt index 2adc8445293..2d3fab79538 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -434,7 +434,7 @@ beautifulsoup4==4.11.1 bellows==0.35.5 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.13.6 +bimmer-connected==0.13.7 # homeassistant.components.bizkaibus bizkaibus==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f470ffb31ee..712b10c12a6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -367,7 +367,7 @@ beautifulsoup4==4.11.1 bellows==0.35.5 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.13.6 +bimmer-connected==0.13.7 # homeassistant.components.bluetooth bleak-retry-connector==3.0.2 From e26b8e11d31202228bf5841f3d544d04fa889a07 Mon Sep 17 00:00:00 2001 From: Hmmbob <33529490+hmmbob@users.noreply.github.com> Date: Thu, 22 Jun 2023 09:35:53 +0200 Subject: [PATCH 79/83] Fix goodwe midnight error (#95041) --- homeassistant/components/goodwe/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/goodwe/sensor.py b/homeassistant/components/goodwe/sensor.py index d76d6202832..4a4296bc526 100644 --- a/homeassistant/components/goodwe/sensor.py +++ b/homeassistant/components/goodwe/sensor.py @@ -243,7 +243,7 @@ class InverterSensor(CoordinatorEntity[GoodweUpdateCoordinator], SensorEntity): In contrast to "total" sensors, these "daily" sensors need to be reset to 0 on midnight. """ if not self.coordinator.last_update_success: - self.coordinator.reset_sensor(self._sensor.id) + self.coordinator.reset_sensor(self._sensor.id_) self.async_write_ha_state() _LOGGER.debug("Goodwe reset %s to 0", self.name) next_midnight = dt_util.start_of_local_day( From 0fa954040eeaa5858b5a02361b139043fddd4f16 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Thu, 22 Jun 2023 11:13:14 +0200 Subject: [PATCH 80/83] Fix removal of orphaned Matter devices (#95044) --- homeassistant/components/matter/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/matter/__init__.py b/homeassistant/components/matter/__init__.py index 4c47cd4d235..59c5ec9efc8 100644 --- a/homeassistant/components/matter/__init__.py +++ b/homeassistant/components/matter/__init__.py @@ -2,11 +2,12 @@ from __future__ import annotations import asyncio +from contextlib import suppress import async_timeout from matter_server.client import MatterClient from matter_server.client.exceptions import CannotConnect, InvalidServerVersion -from matter_server.common.errors import MatterError, NodeCommissionFailed +from matter_server.common.errors import MatterError, NodeCommissionFailed, NodeNotExists import voluptuous as vol from homeassistant.components.hassio import AddonError, AddonManager, AddonState @@ -207,7 +208,9 @@ async def async_remove_config_entry_device( ) matter = get_matter(hass) - await matter.matter_client.remove_node(node.node_id) + with suppress(NodeNotExists): + # ignore if the server has already removed the node. + await matter.matter_client.remove_node(node.node_id) return True From 8f9425f09f6e1f3d4a84b2f31a1b94def40f27df Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Thu, 22 Jun 2023 21:13:10 +0200 Subject: [PATCH 81/83] Fix KNX device trigger passing info data (#95076) --- .../components/knx/device_trigger.py | 3 ++- tests/components/knx/test_device_trigger.py | 20 ++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/knx/device_trigger.py b/homeassistant/components/knx/device_trigger.py index 8a074b43b7d..1abafb221db 100644 --- a/homeassistant/components/knx/device_trigger.py +++ b/homeassistant/components/knx/device_trigger.py @@ -84,6 +84,7 @@ async def async_attach_trigger( trigger_info: TriggerInfo, ) -> CALLBACK_TYPE: """Attach a trigger.""" + trigger_data = trigger_info["trigger_data"] dst_addresses: list[str] = config.get(EXTRA_FIELD_DESTINATION, []) job = HassJob(action, f"KNX device trigger {trigger_info}") knx: KNXModule = hass.data[DOMAIN] @@ -95,7 +96,7 @@ async def async_attach_trigger( return hass.async_run_hass_job( job, - {"trigger": telegram}, + {"trigger": {**trigger_data, **telegram}}, ) return knx.telegrams.async_listen_telegram( diff --git a/tests/components/knx/test_device_trigger.py b/tests/components/knx/test_device_trigger.py index c7063997585..c3d3ed67b03 100644 --- a/tests/components/knx/test_device_trigger.py +++ b/tests/components/knx/test_device_trigger.py @@ -56,6 +56,7 @@ async def test_if_fires_on_telegram( identifiers={(DOMAIN, f"_{knx.mock_config_entry.entry_id}_interface")} ) + # "id" field added to action to test if `trigger_data` passed correctly in `async_attach_trigger` assert await async_setup_component( hass, automation.DOMAIN, @@ -71,7 +72,8 @@ async def test_if_fires_on_telegram( "action": { "service": "test.automation", "data_template": { - "catch_all": ("telegram - {{ trigger.destination }}") + "catch_all": ("telegram - {{ trigger.destination }}"), + "id": (" {{ trigger.id }}"), }, }, }, @@ -82,11 +84,13 @@ async def test_if_fires_on_telegram( "device_id": device_entry.id, "type": "telegram", "destination": ["1/2/3", "1/2/4"], + "id": "test-id", }, "action": { "service": "test.automation", "data_template": { - "specific": ("telegram - {{ trigger.destination }}") + "specific": ("telegram - {{ trigger.destination }}"), + "id": (" {{ trigger.id }}"), }, }, }, @@ -96,12 +100,18 @@ async def test_if_fires_on_telegram( await knx.receive_write("0/0/1", (0x03, 0x2F)) assert len(calls) == 1 - assert calls.pop().data["catch_all"] == "telegram - 0/0/1" + test_call = calls.pop() + assert test_call.data["catch_all"] == "telegram - 0/0/1" + assert test_call.data["id"] == 0 await knx.receive_write("1/2/4", (0x03, 0x2F)) assert len(calls) == 2 - assert calls.pop().data["specific"] == "telegram - 1/2/4" - assert calls.pop().data["catch_all"] == "telegram - 1/2/4" + test_call = calls.pop() + assert test_call.data["specific"] == "telegram - 1/2/4" + assert test_call.data["id"] == "test-id" + test_call = calls.pop() + assert test_call.data["catch_all"] == "telegram - 1/2/4" + assert test_call.data["id"] == 0 async def test_remove_device_trigger( From 4cf9beccd84f516abc647b2ac1411a196476c759 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 23 Jun 2023 16:46:52 +0200 Subject: [PATCH 82/83] Bumped version to 2023.6.3 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 2d5e5ebb14c..71edc76d9dc 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -8,7 +8,7 @@ from .backports.enum import StrEnum APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2023 MINOR_VERSION: Final = 6 -PATCH_VERSION: Final = "2" +PATCH_VERSION: Final = "3" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 10, 0) diff --git a/pyproject.toml b/pyproject.toml index 5f3c1b4f71e..563b07570a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2023.6.2" +version = "2023.6.3" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 9f6dab0643daf63b8d311cd8e44065b63b15e347 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 23 Jun 2023 17:56:25 +0200 Subject: [PATCH 83/83] Remove incompatible config schema for Fully Kiosk --- homeassistant/components/fully_kiosk/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/homeassistant/components/fully_kiosk/__init__.py b/homeassistant/components/fully_kiosk/__init__.py index 8b350433858..217e73e4d1c 100644 --- a/homeassistant/components/fully_kiosk/__init__.py +++ b/homeassistant/components/fully_kiosk/__init__.py @@ -2,7 +2,6 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers import config_validation as cv from homeassistant.helpers.typing import ConfigType from .const import DOMAIN @@ -18,8 +17,6 @@ PLATFORMS = [ Platform.SWITCH, ] -CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN) - async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up Fully Kiosk Browser."""