From 6200fd381e3af95b49f86941e84c282ece665d14 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 26 Jul 2023 16:47:34 +0200 Subject: [PATCH 01/71] Bumped version to 2023.8.0b0 --- 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 513d72555a5..c965cf78528 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from typing import Final APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2023 MINOR_VERSION: Final = 8 -PATCH_VERSION: Final = "0.dev0" +PATCH_VERSION: Final = "0b0" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0) diff --git a/pyproject.toml b/pyproject.toml index 1f179518fd9..a15355dc9cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2023.8.0.dev0" +version = "2023.8.0b0" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From e31a4610f7ece736b995b999537656940a048ece Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Thu, 27 Jul 2023 08:54:20 +0200 Subject: [PATCH 02/71] Fix authlib version constraint required by point (#97228) --- homeassistant/components/point/__init__.py | 5 ++--- homeassistant/package_constraints.txt | 4 ---- script/gen_requirements_all.py | 4 ---- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/point/__init__.py b/homeassistant/components/point/__init__.py index 6600a8240a0..627736f605d 100644 --- a/homeassistant/components/point/__init__.py +++ b/homeassistant/components/point/__init__.py @@ -97,9 +97,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: token_saver=token_saver, ) try: - # pylint: disable-next=fixme - # TODO Remove authlib constraint when refactoring this code - await session.ensure_active_token() + # the call to user() implicitly calls ensure_active_token() in authlib + await session.user() except ConnectTimeout as err: _LOGGER.debug("Connection Timeout") raise ConfigEntryNotReady from err diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index a9239bcfda8..3ce056e1a46 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -121,10 +121,6 @@ python-socketio>=4.6.0,<5.0 # https://github.com/home-assistant/core/pull/67046 multidict>=6.0.2 -# Required for compatibility with point integration - ensure_active_token -# https://github.com/home-assistant/core/pull/68176 -authlib<1.0 - # Version 2.0 added typing, prevent accidental fallbacks backoff>=2.0 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 9302d547786..02d528c33e2 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -123,10 +123,6 @@ python-socketio>=4.6.0,<5.0 # https://github.com/home-assistant/core/pull/67046 multidict>=6.0.2 -# Required for compatibility with point integration - ensure_active_token -# https://github.com/home-assistant/core/pull/68176 -authlib<1.0 - # Version 2.0 added typing, prevent accidental fallbacks backoff>=2.0 From b2adb4edbe7444fb6779360ff46844932b827595 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Thu, 27 Jul 2023 13:30:42 -0500 Subject: [PATCH 03/71] Add wildcards to sentence triggers (#97236) Co-authored-by: Franck Nijhof --- .../components/conversation/__init__.py | 6 +- .../components/conversation/default_agent.py | 44 ++++++++++--- .../components/conversation/manifest.json | 2 +- .../components/conversation/trigger.py | 21 ++++++- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../conversation/snapshots/test_init.ambr | 23 +++++-- .../conversation/test_default_agent.py | 3 +- tests/components/conversation/test_trigger.py | 61 +++++++++++++++++++ 10 files changed, 147 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/conversation/__init__.py b/homeassistant/components/conversation/__init__.py index 30ecf16bb37..29dd56c11ec 100644 --- a/homeassistant/components/conversation/__init__.py +++ b/homeassistant/components/conversation/__init__.py @@ -322,7 +322,11 @@ async def websocket_hass_agent_debug( "intent": { "name": result.intent.name, }, - "entities": { + "slots": { # direct access to values + entity_key: entity.value + for entity_key, entity in result.entities.items() + }, + "details": { entity_key: { "name": entity.name, "value": entity.value, diff --git a/homeassistant/components/conversation/default_agent.py b/homeassistant/components/conversation/default_agent.py index b0a3702b5c9..04aafc8a99d 100644 --- a/homeassistant/components/conversation/default_agent.py +++ b/homeassistant/components/conversation/default_agent.py @@ -11,7 +11,14 @@ from pathlib import Path import re from typing import IO, Any -from hassil.intents import Intents, ResponseType, SlotList, TextSlotList +from hassil.expression import Expression, ListReference, Sequence +from hassil.intents import ( + Intents, + ResponseType, + SlotList, + TextSlotList, + WildcardSlotList, +) from hassil.recognize import RecognizeResult, recognize_all from hassil.util import merge_dict from home_assistant_intents import get_domains_and_languages, get_intents @@ -48,7 +55,7 @@ _ENTITY_REGISTRY_UPDATE_FIELDS = ["aliases", "name", "original_name"] REGEX_TYPE = type(re.compile("")) TRIGGER_CALLBACK_TYPE = Callable[ # pylint: disable=invalid-name - [str], Awaitable[str | None] + [str, RecognizeResult], Awaitable[str | None] ] @@ -657,6 +664,17 @@ class DefaultAgent(AbstractConversationAgent): } self._trigger_intents = Intents.from_dict(intents_dict) + + # Assume slot list references are wildcards + wildcard_names: set[str] = set() + for trigger_intent in self._trigger_intents.intents.values(): + for intent_data in trigger_intent.data: + for sentence in intent_data.sentences: + _collect_list_references(sentence, wildcard_names) + + for wildcard_name in wildcard_names: + self._trigger_intents.slot_lists[wildcard_name] = WildcardSlotList() + _LOGGER.debug("Rebuilt trigger intents: %s", intents_dict) def _unregister_trigger(self, trigger_data: TriggerData) -> None: @@ -682,14 +700,14 @@ class DefaultAgent(AbstractConversationAgent): assert self._trigger_intents is not None - matched_triggers: set[int] = set() + matched_triggers: dict[int, RecognizeResult] = {} for result in recognize_all(sentence, self._trigger_intents): trigger_id = int(result.intent.name) if trigger_id in matched_triggers: # Already matched a sentence from this trigger break - matched_triggers.add(trigger_id) + matched_triggers[trigger_id] = result if not matched_triggers: # Sentence did not match any trigger sentences @@ -699,14 +717,14 @@ class DefaultAgent(AbstractConversationAgent): "'%s' matched %s trigger(s): %s", sentence, len(matched_triggers), - matched_triggers, + list(matched_triggers), ) # Gather callback responses in parallel trigger_responses = await asyncio.gather( *( - self._trigger_sentences[trigger_id].callback(sentence) - for trigger_id in matched_triggers + self._trigger_sentences[trigger_id].callback(sentence, result) + for trigger_id, result in matched_triggers.items() ) ) @@ -733,3 +751,15 @@ def _make_error_result( response.async_set_error(error_code, response_text) return ConversationResult(response, conversation_id) + + +def _collect_list_references(expression: Expression, list_names: set[str]) -> None: + """Collect list reference names recursively.""" + if isinstance(expression, Sequence): + seq: Sequence = expression + for item in seq.items: + _collect_list_references(item, list_names) + elif isinstance(expression, ListReference): + # {list} + list_ref: ListReference = expression + list_names.add(list_ref.slot_name) diff --git a/homeassistant/components/conversation/manifest.json b/homeassistant/components/conversation/manifest.json index a8f24a335f0..1eb58e96ff9 100644 --- a/homeassistant/components/conversation/manifest.json +++ b/homeassistant/components/conversation/manifest.json @@ -7,5 +7,5 @@ "integration_type": "system", "iot_class": "local_push", "quality_scale": "internal", - "requirements": ["hassil==1.2.2", "home-assistant-intents==2023.7.25"] + "requirements": ["hassil==1.2.5", "home-assistant-intents==2023.7.25"] } diff --git a/homeassistant/components/conversation/trigger.py b/homeassistant/components/conversation/trigger.py index b64b74c5fa6..71ddb5c1237 100644 --- a/homeassistant/components/conversation/trigger.py +++ b/homeassistant/components/conversation/trigger.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Any -from hassil.recognize import PUNCTUATION +from hassil.recognize import PUNCTUATION, RecognizeResult import voluptuous as vol from homeassistant.const import CONF_COMMAND, CONF_PLATFORM @@ -49,12 +49,29 @@ async def async_attach_trigger( job = HassJob(action) @callback - async def call_action(sentence: str) -> str | None: + async def call_action(sentence: str, result: RecognizeResult) -> str | None: """Call action with right context.""" + + # Add slot values as extra trigger data + details = { + entity_name: { + "name": entity_name, + "text": entity.text.strip(), # remove whitespace + "value": entity.value.strip() + if isinstance(entity.value, str) + else entity.value, + } + for entity_name, entity in result.entities.items() + } + trigger_input: dict[str, Any] = { # Satisfy type checker **trigger_data, "platform": DOMAIN, "sentence": sentence, + "details": details, + "slots": { # direct access to values + entity_name: entity["value"] for entity_name, entity in details.items() + }, } # Wait for the automation to complete diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 3ce056e1a46..3210d76964e 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -20,7 +20,7 @@ dbus-fast==1.87.2 fnv-hash-fast==0.4.0 ha-av==10.1.0 hass-nabucasa==0.69.0 -hassil==1.2.2 +hassil==1.2.5 home-assistant-bluetooth==1.10.2 home-assistant-frontend==20230725.0 home-assistant-intents==2023.7.25 diff --git a/requirements_all.txt b/requirements_all.txt index edd1edd7c2f..2be19a3052c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -958,7 +958,7 @@ hass-nabucasa==0.69.0 hass-splunk==0.1.1 # homeassistant.components.conversation -hassil==1.2.2 +hassil==1.2.5 # homeassistant.components.jewish_calendar hdate==0.10.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a07488a1898..220c21bada3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -753,7 +753,7 @@ habitipy==0.2.0 hass-nabucasa==0.69.0 # homeassistant.components.conversation -hassil==1.2.2 +hassil==1.2.5 # homeassistant.components.jewish_calendar hdate==0.10.4 diff --git a/tests/components/conversation/snapshots/test_init.ambr b/tests/components/conversation/snapshots/test_init.ambr index 8ef0cef52f9..f9fe284bcb0 100644 --- a/tests/components/conversation/snapshots/test_init.ambr +++ b/tests/components/conversation/snapshots/test_init.ambr @@ -372,7 +372,7 @@ dict({ 'results': list([ dict({ - 'entities': dict({ + 'details': dict({ 'name': dict({ 'name': 'name', 'text': 'my cool light', @@ -382,6 +382,9 @@ 'intent': dict({ 'name': 'HassTurnOn', }), + 'slots': dict({ + 'name': 'my cool light', + }), 'targets': dict({ 'light.kitchen': dict({ 'matched': True, @@ -389,7 +392,7 @@ }), }), dict({ - 'entities': dict({ + 'details': dict({ 'name': dict({ 'name': 'name', 'text': 'my cool light', @@ -399,6 +402,9 @@ 'intent': dict({ 'name': 'HassTurnOff', }), + 'slots': dict({ + 'name': 'my cool light', + }), 'targets': dict({ 'light.kitchen': dict({ 'matched': True, @@ -406,7 +412,7 @@ }), }), dict({ - 'entities': dict({ + 'details': dict({ 'area': dict({ 'name': 'area', 'text': 'kitchen', @@ -421,6 +427,10 @@ 'intent': dict({ 'name': 'HassTurnOn', }), + 'slots': dict({ + 'area': 'kitchen', + 'domain': 'light', + }), 'targets': dict({ 'light.kitchen': dict({ 'matched': True, @@ -428,7 +438,7 @@ }), }), dict({ - 'entities': dict({ + 'details': dict({ 'area': dict({ 'name': 'area', 'text': 'kitchen', @@ -448,6 +458,11 @@ 'intent': dict({ 'name': 'HassGetState', }), + 'slots': dict({ + 'area': 'kitchen', + 'domain': 'light', + 'state': 'on', + }), 'targets': dict({ 'light.kitchen': dict({ 'matched': False, diff --git a/tests/components/conversation/test_default_agent.py b/tests/components/conversation/test_default_agent.py index af9af468453..c3c2e621260 100644 --- a/tests/components/conversation/test_default_agent.py +++ b/tests/components/conversation/test_default_agent.py @@ -246,7 +246,8 @@ async def test_trigger_sentences(hass: HomeAssistant, init_components) -> None: for sentence in test_sentences: callback.reset_mock() result = await conversation.async_converse(hass, sentence, None, Context()) - callback.assert_called_once_with(sentence) + assert callback.call_count == 1 + assert callback.call_args[0][0] == sentence assert ( result.response.response_type == intent.IntentResponseType.ACTION_DONE ), sentence diff --git a/tests/components/conversation/test_trigger.py b/tests/components/conversation/test_trigger.py index 522162fa457..3f4dd9e3a7e 100644 --- a/tests/components/conversation/test_trigger.py +++ b/tests/components/conversation/test_trigger.py @@ -61,6 +61,8 @@ async def test_if_fires_on_event(hass: HomeAssistant, calls, setup_comp) -> None "idx": "0", "platform": "conversation", "sentence": "Ha ha ha", + "slots": {}, + "details": {}, } @@ -103,6 +105,8 @@ async def test_same_trigger_multiple_sentences( "idx": "0", "platform": "conversation", "sentence": "hello", + "slots": {}, + "details": {}, } @@ -188,3 +192,60 @@ async def test_fails_on_punctuation(hass: HomeAssistant, command: str) -> None: }, ], ) + + +async def test_wildcards(hass: HomeAssistant, calls, setup_comp) -> None: + """Test wildcards in trigger sentences.""" + assert await async_setup_component( + hass, + "automation", + { + "automation": { + "trigger": { + "platform": "conversation", + "command": [ + "play {album} by {artist}", + ], + }, + "action": { + "service": "test.automation", + "data_template": {"data": "{{ trigger }}"}, + }, + } + }, + ) + + await hass.services.async_call( + "conversation", + "process", + { + "text": "play the white album by the beatles", + }, + blocking=True, + ) + + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0].data["data"] == { + "alias": None, + "id": "0", + "idx": "0", + "platform": "conversation", + "sentence": "play the white album by the beatles", + "slots": { + "album": "the white album", + "artist": "the beatles", + }, + "details": { + "album": { + "name": "album", + "text": "the white album", + "value": "the white album", + }, + "artist": { + "name": "artist", + "text": "the beatles", + "value": "the beatles", + }, + }, + } From 20df37c1323d649b046c03ede2856791cd047746 Mon Sep 17 00:00:00 2001 From: "J.P. Krauss" Date: Wed, 26 Jul 2023 12:30:25 -0700 Subject: [PATCH 04/71] Improve AirNow Configuration Error Handling (#97267) * Fix config flow error handling when no data is returned by AirNow API * Add test for PyAirNow EmptyResponseError * Typo Fix --- homeassistant/components/airnow/config_flow.py | 4 +++- homeassistant/components/airnow/strings.json | 2 +- tests/components/airnow/test_config_flow.py | 13 ++++++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/airnow/config_flow.py b/homeassistant/components/airnow/config_flow.py index 39dbef48647..67bce66e167 100644 --- a/homeassistant/components/airnow/config_flow.py +++ b/homeassistant/components/airnow/config_flow.py @@ -2,7 +2,7 @@ import logging from pyairnow import WebServiceAPI -from pyairnow.errors import AirNowError, InvalidKeyError +from pyairnow.errors import AirNowError, EmptyResponseError, InvalidKeyError import voluptuous as vol from homeassistant import config_entries, core, exceptions @@ -35,6 +35,8 @@ async def validate_input(hass: core.HomeAssistant, data): raise InvalidAuth from exc except AirNowError as exc: raise CannotConnect from exc + except EmptyResponseError as exc: + raise InvalidLocation from exc if not test_data: raise InvalidLocation diff --git a/homeassistant/components/airnow/strings.json b/homeassistant/components/airnow/strings.json index aed12596176..072f0988c19 100644 --- a/homeassistant/components/airnow/strings.json +++ b/homeassistant/components/airnow/strings.json @@ -14,7 +14,7 @@ "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", - "invalid_location": "No results found for that location", + "invalid_location": "No results found for that location, try changing the location or station radius.", "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { diff --git a/tests/components/airnow/test_config_flow.py b/tests/components/airnow/test_config_flow.py index efa462ee4e6..5fda5f532a3 100644 --- a/tests/components/airnow/test_config_flow.py +++ b/tests/components/airnow/test_config_flow.py @@ -1,7 +1,7 @@ """Test the AirNow config flow.""" from unittest.mock import AsyncMock -from pyairnow.errors import AirNowError, InvalidKeyError +from pyairnow.errors import AirNowError, EmptyResponseError, InvalidKeyError import pytest from homeassistant import config_entries, data_entry_flow @@ -55,6 +55,17 @@ async def test_form_cannot_connect(hass: HomeAssistant, config, setup_airnow) -> assert result2["errors"] == {"base": "cannot_connect"} +@pytest.mark.parametrize("mock_api_get", [AsyncMock(side_effect=EmptyResponseError)]) +async def test_form_empty_result(hass: HomeAssistant, config, setup_airnow) -> None: + """Test we handle empty response error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + result2 = await hass.config_entries.flow.async_configure(result["flow_id"], config) + assert result2["type"] == "form" + assert result2["errors"] == {"base": "invalid_location"} + + @pytest.mark.parametrize("mock_api_get", [AsyncMock(side_effect=RuntimeError)]) async def test_form_unexpected(hass: HomeAssistant, config, setup_airnow) -> None: """Test we handle an unexpected error.""" From 73076fe94dc1a5b17f7c998c50a74d8112182c61 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 26 Jul 2023 21:22:22 +0200 Subject: [PATCH 05/71] Fix zodiac import flow/issue (#97282) --- homeassistant/components/zodiac/__init__.py | 40 ++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/zodiac/__init__.py b/homeassistant/components/zodiac/__init__.py index 81d5b5bdc21..48d1d8aa7aa 100644 --- a/homeassistant/components/zodiac/__init__.py +++ b/homeassistant/components/zodiac/__init__.py @@ -17,27 +17,27 @@ CONFIG_SCHEMA = vol.Schema( async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the zodiac component.""" - - async_create_issue( - hass, - HOMEASSISTANT_DOMAIN, - f"deprecated_yaml_{DOMAIN}", - breaks_in_ha_version="2024.1.0", - is_fixable=False, - issue_domain=DOMAIN, - severity=IssueSeverity.WARNING, - translation_key="deprecated_yaml", - translation_placeholders={ - "domain": DOMAIN, - "integration_title": "Zodiac", - }, - ) - - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data=config + if DOMAIN in config: + async_create_issue( + hass, + HOMEASSISTANT_DOMAIN, + f"deprecated_yaml_{DOMAIN}", + breaks_in_ha_version="2024.1.0", + is_fixable=False, + issue_domain=DOMAIN, + severity=IssueSeverity.WARNING, + translation_key="deprecated_yaml", + translation_placeholders={ + "domain": DOMAIN, + "integration_title": "Zodiac", + }, + ) + + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=config + ) ) - ) return True From d9beeac6750f5dc77e67df7817785204cc55ad0f Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Thu, 27 Jul 2023 09:21:30 +0200 Subject: [PATCH 06/71] Bump aioslimproto to 2.3.3 (#97283) --- homeassistant/components/slimproto/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/slimproto/manifest.json b/homeassistant/components/slimproto/manifest.json index 1ef87e84933..b221db96262 100644 --- a/homeassistant/components/slimproto/manifest.json +++ b/homeassistant/components/slimproto/manifest.json @@ -6,5 +6,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/slimproto", "iot_class": "local_push", - "requirements": ["aioslimproto==2.3.2"] + "requirements": ["aioslimproto==2.3.3"] } diff --git a/requirements_all.txt b/requirements_all.txt index 2be19a3052c..049199b3890 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -345,7 +345,7 @@ aioshelly==5.4.0 aioskybell==22.7.0 # homeassistant.components.slimproto -aioslimproto==2.3.2 +aioslimproto==2.3.3 # homeassistant.components.steamist aiosteamist==0.3.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 220c21bada3..67ab0c73b79 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -320,7 +320,7 @@ aioshelly==5.4.0 aioskybell==22.7.0 # homeassistant.components.slimproto -aioslimproto==2.3.2 +aioslimproto==2.3.3 # homeassistant.components.steamist aiosteamist==0.3.2 From fc6ff69564a07a5270b914ac525fd35f4a3ffd29 Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Wed, 26 Jul 2023 22:03:38 +0200 Subject: [PATCH 07/71] Rename key of water level sensor in PEGELONLINE (#97289) --- homeassistant/components/pegel_online/coordinator.py | 4 ++-- homeassistant/components/pegel_online/model.py | 2 +- homeassistant/components/pegel_online/sensor.py | 8 ++++---- homeassistant/components/pegel_online/strings.json | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/pegel_online/coordinator.py b/homeassistant/components/pegel_online/coordinator.py index 995953c5e36..8fab3ce36ae 100644 --- a/homeassistant/components/pegel_online/coordinator.py +++ b/homeassistant/components/pegel_online/coordinator.py @@ -31,10 +31,10 @@ class PegelOnlineDataUpdateCoordinator(DataUpdateCoordinator[PegelOnlineData]): async def _async_update_data(self) -> PegelOnlineData: """Fetch data from API endpoint.""" try: - current_measurement = await self.api.async_get_station_measurement( + water_level = await self.api.async_get_station_measurement( self.station.uuid ) except CONNECT_ERRORS as err: raise UpdateFailed(f"Failed to communicate with API: {err}") from err - return {"current_measurement": current_measurement} + return {"water_level": water_level} diff --git a/homeassistant/components/pegel_online/model.py b/homeassistant/components/pegel_online/model.py index c1760d3261b..c8dac75bcf2 100644 --- a/homeassistant/components/pegel_online/model.py +++ b/homeassistant/components/pegel_online/model.py @@ -8,4 +8,4 @@ from aiopegelonline import CurrentMeasurement class PegelOnlineData(TypedDict): """TypedDict for PEGELONLINE Coordinator Data.""" - current_measurement: CurrentMeasurement + water_level: CurrentMeasurement diff --git a/homeassistant/components/pegel_online/sensor.py b/homeassistant/components/pegel_online/sensor.py index 7d48635781b..14ec0c2d032 100644 --- a/homeassistant/components/pegel_online/sensor.py +++ b/homeassistant/components/pegel_online/sensor.py @@ -37,11 +37,11 @@ class PegelOnlineSensorEntityDescription( SENSORS: tuple[PegelOnlineSensorEntityDescription, ...] = ( PegelOnlineSensorEntityDescription( - key="current_measurement", - translation_key="current_measurement", + key="water_level", + translation_key="water_level", state_class=SensorStateClass.MEASUREMENT, - fn_native_unit=lambda data: data["current_measurement"].uom, - fn_native_value=lambda data: data["current_measurement"].value, + fn_native_unit=lambda data: data["water_level"].uom, + fn_native_value=lambda data: data["water_level"].value, icon="mdi:waves-arrow-up", ), ) diff --git a/homeassistant/components/pegel_online/strings.json b/homeassistant/components/pegel_online/strings.json index 71ec95f825c..930e349f9c3 100644 --- a/homeassistant/components/pegel_online/strings.json +++ b/homeassistant/components/pegel_online/strings.json @@ -26,7 +26,7 @@ }, "entity": { "sensor": { - "current_measurement": { + "water_level": { "name": "Water level" } } From c48f1b78997077674a044cabec22a3f663e9a331 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Wed, 26 Jul 2023 23:12:01 +0200 Subject: [PATCH 08/71] Weather remove forecast deprecation (#97292) --- homeassistant/components/weather/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index 89bd601fdae..f0c32f2d8cc 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -272,8 +272,6 @@ class WeatherEntity(Entity): "visibility_unit", "_attr_precipitation_unit", "precipitation_unit", - "_attr_forecast", - "forecast", ) ): if _reported is False: From 1b664e6a0b66e6b962b12308dfa98ebde607e36f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 27 Jul 2023 09:22:22 +0200 Subject: [PATCH 09/71] Fix implicit use of device name in TPLink switch (#97293) --- homeassistant/components/tplink/switch.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/tplink/switch.py b/homeassistant/components/tplink/switch.py index d82308a2e32..6c843246663 100644 --- a/homeassistant/components/tplink/switch.py +++ b/homeassistant/components/tplink/switch.py @@ -84,6 +84,8 @@ class SmartPlugLedSwitch(CoordinatedTPLinkEntity, SwitchEntity): class SmartPlugSwitch(CoordinatedTPLinkEntity, SwitchEntity): """Representation of a TPLink Smart Plug switch.""" + _attr_name = None + def __init__( self, device: SmartDevice, From aee6e0e6ebab52ad8115ab0baa433c86d3f15ba4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 27 Jul 2023 02:17:27 -0500 Subject: [PATCH 10/71] Fix dumping lru stats in the profiler (#97303) --- homeassistant/components/profiler/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/profiler/__init__.py b/homeassistant/components/profiler/__init__.py index ba5f25a1c02..8c5c206ae9f 100644 --- a/homeassistant/components/profiler/__init__.py +++ b/homeassistant/components/profiler/__init__.py @@ -45,7 +45,6 @@ _KNOWN_LRU_CLASSES = ( "StatesMetaManager", "StateAttributesManager", "StatisticsMetaManager", - "DomainData", "IntegrationMatcher", ) From c925e1826bb812f2131ac70e5c40b0ac42086302 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Thu, 27 Jul 2023 09:23:23 +0200 Subject: [PATCH 11/71] Set mqtt entity name to `null` when it is a duplicate of the device name (#97304) --- homeassistant/components/mqtt/mixins.py | 9 +++++++++ tests/components/mqtt/test_mixins.py | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 70b681ffbb2..9f0849a4d4c 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -1135,7 +1135,16 @@ class MqttEntity( "MQTT device information always needs to include a name, got %s, " "if device information is shared between multiple entities, the device " "name must be included in each entity's device configuration", + config, ) + elif config[CONF_DEVICE][CONF_NAME] == entity_name: + _LOGGER.warning( + "MQTT device name is equal to entity name in your config %s, " + "this is not expected. Please correct your configuration. " + "The entity name will be set to `null`", + config, + ) + self._attr_name = None def _setup_common_attributes_from_config(self, config: ConfigType) -> None: """(Re)Setup the common attributes for the entity.""" diff --git a/tests/components/mqtt/test_mixins.py b/tests/components/mqtt/test_mixins.py index 5a30a3a65de..23367d7829f 100644 --- a/tests/components/mqtt/test_mixins.py +++ b/tests/components/mqtt/test_mixins.py @@ -212,6 +212,26 @@ async def test_availability_with_shared_state_topic( None, True, ), + ( # entity_name_and_device_name_the_sane + { + mqtt.DOMAIN: { + sensor.DOMAIN: { + "name": "Hello world", + "state_topic": "test-topic", + "unique_id": "veryunique", + "device_class": "humidity", + "device": { + "identifiers": ["helloworld"], + "name": "Hello world", + }, + } + } + }, + "sensor.hello_world", + "Hello world", + "Hello world", + False, + ), ], ids=[ "default_entity_name_without_device_name", @@ -222,6 +242,7 @@ async def test_availability_with_shared_state_topic( "name_set_no_device_name_set", "none_entity_name_with_device_name", "none_entity_name_without_device_name", + "entity_name_and_device_name_the_sane", ], ) @patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SENSOR]) From d6dba4b42b8ad5493010fccde241b5f09f534a0a Mon Sep 17 00:00:00 2001 From: Luke Date: Thu, 27 Jul 2023 03:13:49 -0400 Subject: [PATCH 12/71] bump python-roborock to 0.30.2 (#97306) --- homeassistant/components/roborock/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/roborock/manifest.json b/homeassistant/components/roborock/manifest.json index 5f6aa63ce2f..d26116a7818 100644 --- a/homeassistant/components/roborock/manifest.json +++ b/homeassistant/components/roborock/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/roborock", "iot_class": "local_polling", "loggers": ["roborock"], - "requirements": ["python-roborock==0.30.1"] + "requirements": ["python-roborock==0.30.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 049199b3890..2f9b876a2a1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2150,7 +2150,7 @@ python-qbittorrent==0.4.3 python-ripple-api==0.0.3 # homeassistant.components.roborock -python-roborock==0.30.1 +python-roborock==0.30.2 # homeassistant.components.smarttub python-smarttub==0.0.33 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 67ab0c73b79..3742431f646 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1579,7 +1579,7 @@ python-picnic-api==1.1.0 python-qbittorrent==0.4.3 # homeassistant.components.roborock -python-roborock==0.30.1 +python-roborock==0.30.2 # homeassistant.components.smarttub python-smarttub==0.0.33 From 4eb37172a8f78181a921cd04837081834cb99e9d Mon Sep 17 00:00:00 2001 From: Markus Becker Date: Thu, 27 Jul 2023 08:58:52 +0200 Subject: [PATCH 13/71] Fix typo Lomng -> Long (#97315) --- homeassistant/components/matter/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/matter/strings.json b/homeassistant/components/matter/strings.json index bfdba33327b..c68b38bbb8c 100644 --- a/homeassistant/components/matter/strings.json +++ b/homeassistant/components/matter/strings.json @@ -52,7 +52,7 @@ "state": { "switch_latched": "Switch latched", "initial_press": "Initial press", - "long_press": "Lomng press", + "long_press": "Long press", "short_release": "Short release", "long_release": "Long release", "multi_press_ongoing": "Multi press ongoing", From 52ce21f3b681343803b55d52e76f9d27b6eca85d Mon Sep 17 00:00:00 2001 From: G Johansson Date: Thu, 27 Jul 2023 09:24:32 +0200 Subject: [PATCH 14/71] Fix sql entities not loading (#97316) --- homeassistant/components/sql/sensor.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/sql/sensor.py b/homeassistant/components/sql/sensor.py index cbdef90f623..0c8e90b8895 100644 --- a/homeassistant/components/sql/sensor.py +++ b/homeassistant/components/sql/sensor.py @@ -84,7 +84,11 @@ async def async_setup_platform( if value_template is not None: value_template.hass = hass - trigger_entity_config = {CONF_NAME: name, CONF_DEVICE_CLASS: device_class} + trigger_entity_config = { + CONF_NAME: name, + CONF_DEVICE_CLASS: device_class, + CONF_UNIQUE_ID: unique_id, + } if availability: trigger_entity_config[CONF_AVAILABILITY] = availability if icon: @@ -132,7 +136,11 @@ async def async_setup_entry( value_template.hass = hass name_template = Template(name, hass) - trigger_entity_config = {CONF_NAME: name_template, CONF_DEVICE_CLASS: device_class} + trigger_entity_config = { + CONF_NAME: name_template, + CONF_DEVICE_CLASS: device_class, + CONF_UNIQUE_ID: entry.entry_id, + } await async_setup_sensor( hass, @@ -269,7 +277,6 @@ async def async_setup_sensor( column_name, unit, value_template, - unique_id, yaml, state_class, use_database_executor, @@ -322,7 +329,6 @@ class SQLSensor(ManualTriggerEntity, SensorEntity): column: str, unit: str | None, value_template: Template | None, - unique_id: str | None, yaml: bool, state_class: SensorStateClass | None, use_database_executor: bool, @@ -336,14 +342,16 @@ class SQLSensor(ManualTriggerEntity, SensorEntity): self._column_name = column self.sessionmaker = sessmaker self._attr_extra_state_attributes = {} - self._attr_unique_id = unique_id self._use_database_executor = use_database_executor self._lambda_stmt = _generate_lambda_stmt(query) + self._attr_name = ( + None if not yaml else trigger_entity_config[CONF_NAME].template + ) self._attr_has_entity_name = not yaml - if not yaml and unique_id: + if not yaml and trigger_entity_config.get(CONF_UNIQUE_ID): self._attr_device_info = DeviceInfo( entry_type=DeviceEntryType.SERVICE, - identifiers={(DOMAIN, unique_id)}, + identifiers={(DOMAIN, trigger_entity_config[CONF_UNIQUE_ID])}, manufacturer="SQL", name=trigger_entity_config[CONF_NAME].template, ) From 216383437508f3813321390640da3fd2246a6224 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 27 Jul 2023 18:57:01 +0200 Subject: [PATCH 15/71] Fix DeviceInfo configuration_url validation (#97319) --- homeassistant/helpers/device_registry.py | 41 ++++++++------ homeassistant/helpers/entity.py | 3 +- tests/helpers/test_device_registry.py | 68 ++++++++++++++++++++++-- tests/helpers/test_entity_platform.py | 5 -- 4 files changed, 92 insertions(+), 25 deletions(-) diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index f1eed86f10c..5764f65957e 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -10,6 +10,7 @@ from typing import TYPE_CHECKING, Any, Literal, TypedDict, TypeVar, cast from urllib.parse import urlparse import attr +from yarl import URL from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP from homeassistant.core import Event, HomeAssistant, callback @@ -48,6 +49,8 @@ ORPHANED_DEVICE_KEEP_SECONDS = 86400 * 30 RUNTIME_ONLY_ATTRS = {"suggested_area"} +CONFIGURATION_URL_SCHEMES = {"http", "https", "homeassistant"} + class DeviceEntryDisabler(StrEnum): """What disabled a device entry.""" @@ -168,28 +171,36 @@ def _validate_device_info( ), ) - if (config_url := device_info.get("configuration_url")) is not None: - if type(config_url) is not str or urlparse(config_url).scheme not in [ - "http", - "https", - "homeassistant", - ]: - raise DeviceInfoError( - config_entry.domain if config_entry else "unknown", - device_info, - f"invalid configuration_url '{config_url}'", - ) - return device_info_type +def _validate_configuration_url(value: Any) -> str | None: + """Validate and convert configuration_url.""" + if value is None: + return None + if ( + isinstance(value, URL) + and (value.scheme not in CONFIGURATION_URL_SCHEMES or not value.host) + ) or ( + (parsed_url := urlparse(str(value))) + and ( + parsed_url.scheme not in CONFIGURATION_URL_SCHEMES + or not parsed_url.hostname + ) + ): + raise ValueError(f"invalid configuration_url '{value}'") + return str(value) + + @attr.s(slots=True, frozen=True) class DeviceEntry: """Device Registry Entry.""" area_id: str | None = attr.ib(default=None) config_entries: set[str] = attr.ib(converter=set, factory=set) - configuration_url: str | None = attr.ib(default=None) + configuration_url: str | URL | None = attr.ib( + converter=_validate_configuration_url, default=None + ) connections: set[tuple[str, str]] = attr.ib(converter=set, factory=set) disabled_by: DeviceEntryDisabler | None = attr.ib(default=None) entry_type: DeviceEntryType | None = attr.ib(default=None) @@ -453,7 +464,7 @@ class DeviceRegistry: self, *, config_entry_id: str, - configuration_url: str | None | UndefinedType = UNDEFINED, + configuration_url: str | URL | None | UndefinedType = UNDEFINED, connections: set[tuple[str, str]] | None | UndefinedType = UNDEFINED, default_manufacturer: str | None | UndefinedType = UNDEFINED, default_model: str | None | UndefinedType = UNDEFINED, @@ -582,7 +593,7 @@ class DeviceRegistry: *, add_config_entry_id: str | UndefinedType = UNDEFINED, area_id: str | None | UndefinedType = UNDEFINED, - configuration_url: str | None | UndefinedType = UNDEFINED, + configuration_url: str | URL | None | UndefinedType = UNDEFINED, disabled_by: DeviceEntryDisabler | None | UndefinedType = UNDEFINED, entry_type: DeviceEntryType | None | UndefinedType = UNDEFINED, hw_version: str | None | UndefinedType = UNDEFINED, diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 8e07897c84f..7d240cc0320 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -15,6 +15,7 @@ from timeit import default_timer as timer from typing import TYPE_CHECKING, Any, Final, Literal, TypedDict, TypeVar, final import voluptuous as vol +from yarl import URL from homeassistant.backports.functools import cached_property from homeassistant.config import DATA_CUSTOMIZE @@ -177,7 +178,7 @@ def get_unit_of_measurement(hass: HomeAssistant, entity_id: str) -> str | None: class DeviceInfo(TypedDict, total=False): """Entity device information for device registry.""" - configuration_url: str | None + configuration_url: str | URL | None connections: set[tuple[str, str]] default_manufacturer: str default_model: str diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index 3e59b08cfa8..0210d7ba75d 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -1,9 +1,11 @@ """Tests for the Device Registry.""" +from contextlib import nullcontext import time from typing import Any from unittest.mock import patch import pytest +from yarl import URL from homeassistant import config_entries from homeassistant.const import EVENT_HOMEASSISTANT_STARTED @@ -171,7 +173,7 @@ async def test_loading_from_storage( { "area_id": "12345A", "config_entries": ["1234"], - "configuration_url": "configuration_url", + "configuration_url": "https://example.com/config", "connections": [["Zigbee", "01.23.45.67.89"]], "disabled_by": dr.DeviceEntryDisabler.USER, "entry_type": dr.DeviceEntryType.SERVICE, @@ -213,7 +215,7 @@ async def test_loading_from_storage( assert entry == dr.DeviceEntry( area_id="12345A", config_entries={"1234"}, - configuration_url="configuration_url", + configuration_url="https://example.com/config", connections={("Zigbee", "01.23.45.67.89")}, disabled_by=dr.DeviceEntryDisabler.USER, entry_type=dr.DeviceEntryType.SERVICE, @@ -916,7 +918,7 @@ async def test_update( updated_entry = device_registry.async_update_device( entry.id, area_id="12345A", - configuration_url="configuration_url", + configuration_url="https://example.com/config", disabled_by=dr.DeviceEntryDisabler.USER, entry_type=dr.DeviceEntryType.SERVICE, hw_version="hw_version", @@ -935,7 +937,7 @@ async def test_update( assert updated_entry == dr.DeviceEntry( area_id="12345A", config_entries={"1234"}, - configuration_url="configuration_url", + configuration_url="https://example.com/config", connections={("mac", "12:34:56:ab:cd:ef")}, disabled_by=dr.DeviceEntryDisabler.USER, entry_type=dr.DeviceEntryType.SERVICE, @@ -1670,3 +1672,61 @@ async def test_only_disable_device_if_all_config_entries_are_disabled( entry1 = device_registry.async_get(entry1.id) assert not entry1.disabled + + +@pytest.mark.parametrize( + ("configuration_url", "expectation"), + [ + ("http://localhost", nullcontext()), + ("http://localhost:8123", nullcontext()), + ("https://example.com", nullcontext()), + ("http://localhost/config", nullcontext()), + ("http://localhost:8123/config", nullcontext()), + ("https://example.com/config", nullcontext()), + ("homeassistant://config", nullcontext()), + (URL("http://localhost"), nullcontext()), + (URL("http://localhost:8123"), nullcontext()), + (URL("https://example.com"), nullcontext()), + (URL("http://localhost/config"), nullcontext()), + (URL("http://localhost:8123/config"), nullcontext()), + (URL("https://example.com/config"), nullcontext()), + (URL("homeassistant://config"), nullcontext()), + (None, nullcontext()), + ("http://", pytest.raises(ValueError)), + ("https://", pytest.raises(ValueError)), + ("gopher://localhost", pytest.raises(ValueError)), + ("homeassistant://", pytest.raises(ValueError)), + (URL("http://"), pytest.raises(ValueError)), + (URL("https://"), pytest.raises(ValueError)), + (URL("gopher://localhost"), pytest.raises(ValueError)), + (URL("homeassistant://"), pytest.raises(ValueError)), + # Exception implements __str__ + (Exception("https://example.com"), nullcontext()), + (Exception("https://"), pytest.raises(ValueError)), + (Exception(), pytest.raises(ValueError)), + ], +) +async def test_device_info_configuration_url_validation( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + configuration_url: str | URL | None, + expectation, +) -> None: + """Test configuration URL of device info is properly validated.""" + with expectation: + device_registry.async_get_or_create( + config_entry_id="1234", + identifiers={("something", "1234")}, + name="name", + configuration_url=configuration_url, + ) + + update_device = device_registry.async_get_or_create( + config_entry_id="5678", + identifiers={("something", "5678")}, + name="name", + ) + with expectation: + device_registry.async_update_device( + update_device.id, configuration_url=configuration_url + ) diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index 1f7e579ea95..3eaad662d8b 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -1857,11 +1857,6 @@ async def test_device_name_defaulting_config_entry( "name": "bla", "default_name": "yo", }, - # Invalid configuration URL - { - "identifiers": {("hue", "1234")}, - "configuration_url": "foo://192.168.0.100/config", - }, ], ) async def test_device_type_error_checking( From d05efe8c6afeb13f3101266a08d86bfc61719653 Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Thu, 27 Jul 2023 16:00:27 +0200 Subject: [PATCH 16/71] Duotecno beta fix (#97325) * Fix duotecno * Implement comments * small cover fix --- homeassistant/components/duotecno/__init__.py | 2 +- homeassistant/components/duotecno/cover.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/duotecno/__init__.py b/homeassistant/components/duotecno/__init__.py index 668a38dae5b..98003c3e8c4 100644 --- a/homeassistant/components/duotecno/__init__.py +++ b/homeassistant/components/duotecno/__init__.py @@ -22,10 +22,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await controller.connect( entry.data[CONF_HOST], entry.data[CONF_PORT], entry.data[CONF_PASSWORD] ) - await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) except (OSError, InvalidPassword, LoadFailure) as err: raise ConfigEntryNotReady from err hass.data.setdefault(DOMAIN, {})[entry.entry_id] = controller + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True diff --git a/homeassistant/components/duotecno/cover.py b/homeassistant/components/duotecno/cover.py index 13e3df8fc0a..0fd212df085 100644 --- a/homeassistant/components/duotecno/cover.py +++ b/homeassistant/components/duotecno/cover.py @@ -26,7 +26,7 @@ async def async_setup_entry( """Set up the duoswitch endities.""" cntrl = hass.data[DOMAIN][entry.entry_id] async_add_entities( - DuotecnoCover(channel) for channel in cntrl.get_units("DuoSwitchUnit") + DuotecnoCover(channel) for channel in cntrl.get_units("DuoswitchUnit") ) From 37e9fff1eb7e5e3c3beb25e12bbc2b257ecce811 Mon Sep 17 00:00:00 2001 From: David Knowles Date: Thu, 27 Jul 2023 09:57:36 -0400 Subject: [PATCH 17/71] Fix Hydrawise zone addressing (#97333) --- .../components/hydrawise/binary_sensor.py | 2 +- homeassistant/components/hydrawise/sensor.py | 2 +- homeassistant/components/hydrawise/switch.py | 16 ++++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/hydrawise/binary_sensor.py b/homeassistant/components/hydrawise/binary_sensor.py index bc9b8722c58..63fe28cd400 100644 --- a/homeassistant/components/hydrawise/binary_sensor.py +++ b/homeassistant/components/hydrawise/binary_sensor.py @@ -92,6 +92,6 @@ class HydrawiseBinarySensor(HydrawiseEntity, BinarySensorEntity): if self.entity_description.key == "status": self._attr_is_on = self.coordinator.api.status == "All good!" elif self.entity_description.key == "is_watering": - relay_data = self.coordinator.api.relays[self.data["relay"] - 1] + relay_data = self.coordinator.api.relays_by_zone_number[self.data["relay"]] self._attr_is_on = relay_data["timestr"] == "Now" super()._handle_coordinator_update() diff --git a/homeassistant/components/hydrawise/sensor.py b/homeassistant/components/hydrawise/sensor.py index 9214b9daeac..fa82c058f5b 100644 --- a/homeassistant/components/hydrawise/sensor.py +++ b/homeassistant/components/hydrawise/sensor.py @@ -77,7 +77,7 @@ class HydrawiseSensor(HydrawiseEntity, SensorEntity): def _handle_coordinator_update(self) -> None: """Get the latest data and updates the states.""" LOGGER.debug("Updating Hydrawise sensor: %s", self.name) - relay_data = self.coordinator.api.relays[self.data["relay"] - 1] + relay_data = self.coordinator.api.relays_by_zone_number[self.data["relay"]] if self.entity_description.key == "watering_time": if relay_data["timestr"] == "Now": self._attr_native_value = int(relay_data["run"] / 60) diff --git a/homeassistant/components/hydrawise/switch.py b/homeassistant/components/hydrawise/switch.py index dbd2c08b28e..0dd694a47d6 100644 --- a/homeassistant/components/hydrawise/switch.py +++ b/homeassistant/components/hydrawise/switch.py @@ -99,26 +99,26 @@ class HydrawiseSwitch(HydrawiseEntity, SwitchEntity): def turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" - relay_data = self.data["relay"] - 1 + zone_number = self.data["relay"] if self.entity_description.key == "manual_watering": - self.coordinator.api.run_zone(self._default_watering_timer, relay_data) + self.coordinator.api.run_zone(self._default_watering_timer, zone_number) elif self.entity_description.key == "auto_watering": - self.coordinator.api.suspend_zone(0, relay_data) + self.coordinator.api.suspend_zone(0, zone_number) def turn_off(self, **kwargs: Any) -> None: """Turn the device off.""" - relay_data = self.data["relay"] - 1 + zone_number = self.data["relay"] if self.entity_description.key == "manual_watering": - self.coordinator.api.run_zone(0, relay_data) + self.coordinator.api.run_zone(0, zone_number) elif self.entity_description.key == "auto_watering": - self.coordinator.api.suspend_zone(365, relay_data) + self.coordinator.api.suspend_zone(365, zone_number) @callback def _handle_coordinator_update(self) -> None: """Update device state.""" - relay_data = self.data["relay"] - 1 + zone_number = self.data["relay"] LOGGER.debug("Updating Hydrawise switch: %s", self.name) - timestr = self.coordinator.api.relays[relay_data]["timestr"] + timestr = self.coordinator.api.relays_by_zone_number[zone_number]["timestr"] if self.entity_description.key == "manual_watering": self._attr_is_on = timestr == "Now" elif self.entity_description.key == "auto_watering": From 3028d40e7c35145fa19924c36e554158595a4481 Mon Sep 17 00:00:00 2001 From: David Knowles Date: Thu, 27 Jul 2023 09:54:44 -0400 Subject: [PATCH 18/71] Bump pydrawise to 2023.7.1 (#97334) --- homeassistant/components/hydrawise/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/hydrawise/manifest.json b/homeassistant/components/hydrawise/manifest.json index 48c9cdcf042..d9e6d809960 100644 --- a/homeassistant/components/hydrawise/manifest.json +++ b/homeassistant/components/hydrawise/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/hydrawise", "iot_class": "cloud_polling", "loggers": ["pydrawise"], - "requirements": ["pydrawise==2023.7.0"] + "requirements": ["pydrawise==2023.7.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 2f9b876a2a1..f99b58edd3e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1644,7 +1644,7 @@ pydiscovergy==2.0.1 pydoods==1.0.2 # homeassistant.components.hydrawise -pydrawise==2023.7.0 +pydrawise==2023.7.1 # homeassistant.components.android_ip_webcam pydroid-ipcam==2.0.0 From d7af1acf287887f36505bcd38984e32413cb9211 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 27 Jul 2023 09:30:31 -0500 Subject: [PATCH 19/71] Bump aioesphomeapi to 15.1.15 (#97335) changelog: https://github.com/esphome/aioesphomeapi/compare/v15.1.14...v15.1.15 --- homeassistant/components/esphome/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/esphome/manifest.json b/homeassistant/components/esphome/manifest.json index b9b235ab41e..d35cf90c60f 100644 --- a/homeassistant/components/esphome/manifest.json +++ b/homeassistant/components/esphome/manifest.json @@ -16,7 +16,7 @@ "loggers": ["aioesphomeapi", "noiseprotocol"], "requirements": [ "async_interrupt==1.1.1", - "aioesphomeapi==15.1.14", + "aioesphomeapi==15.1.15", "bluetooth-data-tools==1.6.1", "esphome-dashboard-api==1.2.3" ], diff --git a/requirements_all.txt b/requirements_all.txt index f99b58edd3e..35563221833 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -231,7 +231,7 @@ aioecowitt==2023.5.0 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==15.1.14 +aioesphomeapi==15.1.15 # homeassistant.components.flo aioflo==2021.11.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3742431f646..9f3e1c60dd1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -212,7 +212,7 @@ aioecowitt==2023.5.0 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==15.1.14 +aioesphomeapi==15.1.15 # homeassistant.components.flo aioflo==2021.11.0 From 7dc9204346ad68ffda64579e015bcc3d5bbb3a71 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Thu, 27 Jul 2023 16:58:09 +0200 Subject: [PATCH 20/71] Hue event entity follow up (#97336) --- homeassistant/components/hue/const.py | 4 +- homeassistant/components/hue/logbook.py | 78 ------------- homeassistant/components/hue/strings.json | 3 +- .../components/hue/test_device_trigger_v2.py | 1 + tests/components/hue/test_event.py | 1 + tests/components/hue/test_logbook.py | 107 ------------------ 6 files changed, 6 insertions(+), 188 deletions(-) delete mode 100644 homeassistant/components/hue/logbook.py delete mode 100644 tests/components/hue/test_logbook.py diff --git a/homeassistant/components/hue/const.py b/homeassistant/components/hue/const.py index d7d254b64a8..38c2587bc1a 100644 --- a/homeassistant/components/hue/const.py +++ b/homeassistant/components/hue/const.py @@ -43,11 +43,11 @@ REQUEST_REFRESH_DELAY = 0.3 # V2 API SPECIFIC CONSTANTS ################## DEFAULT_BUTTON_EVENT_TYPES = ( - # I have never ever seen the `DOUBLE_SHORT_RELEASE` - # or `DOUBLE_SHORT_RELEASE` events so leave them out here + # I have never ever seen the `DOUBLE_SHORT_RELEASE` event so leave it out here ButtonEvent.INITIAL_PRESS, ButtonEvent.REPEAT, ButtonEvent.SHORT_RELEASE, + ButtonEvent.LONG_PRESS, ButtonEvent.LONG_RELEASE, ) diff --git a/homeassistant/components/hue/logbook.py b/homeassistant/components/hue/logbook.py deleted file mode 100644 index 21d0da074a7..00000000000 --- a/homeassistant/components/hue/logbook.py +++ /dev/null @@ -1,78 +0,0 @@ -"""Describe hue logbook events.""" -from __future__ import annotations - -from collections.abc import Callable - -from homeassistant.components.logbook import LOGBOOK_ENTRY_MESSAGE, LOGBOOK_ENTRY_NAME -from homeassistant.const import CONF_DEVICE_ID, CONF_EVENT, CONF_ID, CONF_TYPE -from homeassistant.core import Event, HomeAssistant, callback -from homeassistant.helpers import device_registry as dr - -from .const import ATTR_HUE_EVENT, CONF_SUBTYPE, DOMAIN - -TRIGGER_SUBTYPE = { - "button_1": "first button", - "button_2": "second button", - "button_3": "third button", - "button_4": "fourth button", - "double_buttons_1_3": "first and third buttons", - "double_buttons_2_4": "second and fourth buttons", - "dim_down": "dim down", - "dim_up": "dim up", - "turn_off": "turn off", - "turn_on": "turn on", - "1": "first button", - "2": "second button", - "3": "third button", - "4": "fourth button", - "clock_wise": "Rotation clockwise", - "counter_clock_wise": "Rotation counter-clockwise", -} -TRIGGER_TYPE = { - "remote_button_long_release": "{subtype} released after long press", - "remote_button_short_press": "{subtype} pressed", - "remote_button_short_release": "{subtype} released", - "remote_double_button_long_press": "both {subtype} released after long press", - "remote_double_button_short_press": "both {subtype} released", - "initial_press": "{subtype} pressed initially", - "long_press": "{subtype} long press", - "repeat": "{subtype} held down", - "short_release": "{subtype} released after short press", - "long_release": "{subtype} released after long press", - "double_short_release": "both {subtype} released", - "start": '"{subtype}" pressed initially', -} - -UNKNOWN_TYPE = "unknown type" -UNKNOWN_SUB_TYPE = "unknown sub type" - - -@callback -def async_describe_events( - hass: HomeAssistant, - async_describe_event: Callable[[str, str, Callable[[Event], dict[str, str]]], None], -) -> None: - """Describe hue logbook events.""" - - @callback - def async_describe_hue_event(event: Event) -> dict[str, str]: - """Describe hue logbook event.""" - data = event.data - name: str | None = None - if dev_ent := dr.async_get(hass).async_get(data[CONF_DEVICE_ID]): - name = dev_ent.name - if name is None: - name = data[CONF_ID] - if CONF_TYPE in data: # v2 - subtype = TRIGGER_SUBTYPE.get(str(data[CONF_SUBTYPE]), UNKNOWN_SUB_TYPE) - message = TRIGGER_TYPE.get(data[CONF_TYPE], UNKNOWN_TYPE).format( - subtype=subtype - ) - else: - message = f"Event {data[CONF_EVENT]}" # v1 - return { - LOGBOOK_ENTRY_NAME: name, - LOGBOOK_ENTRY_MESSAGE: message, - } - - async_describe_event(DOMAIN, ATTR_HUE_EVENT, async_describe_hue_event) diff --git a/homeassistant/components/hue/strings.json b/homeassistant/components/hue/strings.json index a6920293ac1..6d65abc8d5f 100644 --- a/homeassistant/components/hue/strings.json +++ b/homeassistant/components/hue/strings.json @@ -76,7 +76,8 @@ "initial_press": "Initial press", "repeat": "Repeat", "short_release": "Short press", - "long_release": "Long press", + "long_press": "Long press", + "long_release": "Long release", "double_short_release": "Double press" } } diff --git a/tests/components/hue/test_device_trigger_v2.py b/tests/components/hue/test_device_trigger_v2.py index bfc0b612c1f..e89f53af73a 100644 --- a/tests/components/hue/test_device_trigger_v2.py +++ b/tests/components/hue/test_device_trigger_v2.py @@ -94,6 +94,7 @@ async def test_get_triggers( ButtonEvent.INITIAL_PRESS, ButtonEvent.LONG_RELEASE, ButtonEvent.REPEAT, + ButtonEvent.LONG_PRESS, ButtonEvent.SHORT_RELEASE, ) for control_id, resource_id in ( diff --git a/tests/components/hue/test_event.py b/tests/components/hue/test_event.py index e3f50318f61..4dbb104357d 100644 --- a/tests/components/hue/test_event.py +++ b/tests/components/hue/test_event.py @@ -28,6 +28,7 @@ async def test_event( "initial_press", "repeat", "short_release", + "long_press", "long_release", ] # trigger firing 'initial_press' event from the device diff --git a/tests/components/hue/test_logbook.py b/tests/components/hue/test_logbook.py deleted file mode 100644 index 3f49efcdeb7..00000000000 --- a/tests/components/hue/test_logbook.py +++ /dev/null @@ -1,107 +0,0 @@ -"""The tests for hue logbook.""" -from homeassistant.components.hue.const import ATTR_HUE_EVENT, CONF_SUBTYPE, DOMAIN -from homeassistant.components.hue.v1.hue_event import CONF_LAST_UPDATED -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - CONF_DEVICE_ID, - CONF_EVENT, - CONF_ID, - CONF_TYPE, - CONF_UNIQUE_ID, -) -from homeassistant.core import HomeAssistant -from homeassistant.helpers import device_registry as dr -from homeassistant.setup import async_setup_component - -from .conftest import setup_platform - -from tests.components.logbook.common import MockRow, mock_humanify - -# v1 event -SAMPLE_V1_EVENT = { - CONF_DEVICE_ID: "fe346f17a9f8c15be633f9cc3f3d6631", - CONF_EVENT: 18, - CONF_ID: "hue_tap", - CONF_LAST_UPDATED: "2019-12-28T22:58:03", - CONF_UNIQUE_ID: "00:00:00:00:00:44:23:08-f2", -} -# v2 event -SAMPLE_V2_EVENT = { - CONF_DEVICE_ID: "f974028e7933aea703a2199a855bc4a3", - CONF_ID: "wall_switch_with_2_controls_button", - CONF_SUBTYPE: 1, - CONF_TYPE: "initial_press", - CONF_UNIQUE_ID: "c658d3d8-a013-4b81-8ac6-78b248537e70", -} - - -async def test_humanify_hue_events( - hass: HomeAssistant, mock_bridge_v2, device_registry: dr.DeviceRegistry -) -> None: - """Test hue events when the devices are present in the registry.""" - await setup_platform(hass, mock_bridge_v2, "sensor") - hass.config.components.add("recorder") - assert await async_setup_component(hass, "logbook", {}) - await hass.async_block_till_done() - entry: ConfigEntry = hass.config_entries.async_entries(DOMAIN)[0] - - v1_device = device_registry.async_get_or_create( - identifiers={(DOMAIN, "v1")}, name="Remote 1", config_entry_id=entry.entry_id - ) - v2_device = device_registry.async_get_or_create( - identifiers={(DOMAIN, "v2")}, name="Remote 2", config_entry_id=entry.entry_id - ) - - (v1_event, v2_event) = mock_humanify( - hass, - [ - MockRow( - ATTR_HUE_EVENT, - {**SAMPLE_V1_EVENT, CONF_DEVICE_ID: v1_device.id}, - ), - MockRow( - ATTR_HUE_EVENT, - {**SAMPLE_V2_EVENT, CONF_DEVICE_ID: v2_device.id}, - ), - ], - ) - - assert v1_event["name"] == "Remote 1" - assert v1_event["domain"] == DOMAIN - assert v1_event["message"] == "Event 18" - - assert v2_event["name"] == "Remote 2" - assert v2_event["domain"] == DOMAIN - assert v2_event["message"] == "first button pressed initially" - - -async def test_humanify_hue_events_devices_removed( - hass: HomeAssistant, mock_bridge_v2 -) -> None: - """Test hue events when the devices have been removed from the registry.""" - await setup_platform(hass, mock_bridge_v2, "sensor") - hass.config.components.add("recorder") - assert await async_setup_component(hass, "logbook", {}) - await hass.async_block_till_done() - - (v1_event, v2_event) = mock_humanify( - hass, - [ - MockRow( - ATTR_HUE_EVENT, - SAMPLE_V1_EVENT, - ), - MockRow( - ATTR_HUE_EVENT, - SAMPLE_V2_EVENT, - ), - ], - ) - - assert v1_event["name"] == "hue_tap" - assert v1_event["domain"] == DOMAIN - assert v1_event["message"] == "Event 18" - - assert v2_event["name"] == "wall_switch_with_2_controls_button" - assert v2_event["domain"] == DOMAIN - assert v2_event["message"] == "first button pressed initially" From e4246902fb3498ea2097bc0ea8e401fad4eabe07 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Thu, 27 Jul 2023 15:32:53 +0100 Subject: [PATCH 21/71] Split availability and data subscriptions in homekit_controller (#97337) --- .../components/homekit_controller/connection.py | 16 ++++++++++++---- .../components/homekit_controller/entity.py | 4 +++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index d101517e002..4ba22317644 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -142,7 +142,7 @@ class HKDevice: function=self.async_update, ) - self._all_subscribers: set[CALLBACK_TYPE] = set() + self._availability_callbacks: set[CALLBACK_TYPE] = set() self._subscriptions: dict[tuple[int, int], set[CALLBACK_TYPE]] = {} @property @@ -189,7 +189,7 @@ class HKDevice: if self.available == available: return self.available = available - for callback_ in self._all_subscribers: + for callback_ in self._availability_callbacks: callback_() async def _async_populate_ble_accessory_state(self, event: Event) -> None: @@ -811,12 +811,10 @@ class HKDevice: self, characteristics: Iterable[tuple[int, int]], callback_: CALLBACK_TYPE ) -> CALLBACK_TYPE: """Add characteristics to the watch list.""" - self._all_subscribers.add(callback_) for aid_iid in characteristics: self._subscriptions.setdefault(aid_iid, set()).add(callback_) def _unsub(): - self._all_subscribers.remove(callback_) for aid_iid in characteristics: self._subscriptions[aid_iid].remove(callback_) if not self._subscriptions[aid_iid]: @@ -824,6 +822,16 @@ class HKDevice: return _unsub + @callback + def async_subscribe_availability(self, callback_: CALLBACK_TYPE) -> CALLBACK_TYPE: + """Add characteristics to the watch list.""" + self._availability_callbacks.add(callback_) + + def _unsub(): + self._availability_callbacks.remove(callback_) + + return _unsub + async def get_characteristics(self, *args: Any, **kwargs: Any) -> dict[str, Any]: """Read latest state from homekit accessory.""" return await self.pairing.get_characteristics(*args, **kwargs) diff --git a/homeassistant/components/homekit_controller/entity.py b/homeassistant/components/homekit_controller/entity.py index f6aadfac7ac..046dc9f17ec 100644 --- a/homeassistant/components/homekit_controller/entity.py +++ b/homeassistant/components/homekit_controller/entity.py @@ -58,7 +58,9 @@ class HomeKitEntity(Entity): self.all_characteristics, self._async_write_ha_state ) ) - + self.async_on_remove( + self._accessory.async_subscribe_availability(self._async_write_ha_state) + ) self._accessory.add_pollable_characteristics(self.pollable_characteristics) await self._accessory.add_watchable_characteristics( self.watchable_characteristics From 80092dabdf7b6f838b4098a9bf9561785e491df8 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 27 Jul 2023 18:57:13 +0200 Subject: [PATCH 22/71] Add urllib3<2 package constraint (#97339) --- homeassistant/package_constraints.txt | 4 +++- script/gen_requirements_all.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 3210d76964e..a0046569eb8 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -59,7 +59,9 @@ zeroconf==0.71.4 pycryptodome>=3.6.6 # Constrain urllib3 to ensure we deal with CVE-2020-26137 and CVE-2021-33503 -urllib3>=1.26.5 +# Temporary setting an upper bound, to prevent compat issues with urllib3>=2 +# https://github.com/home-assistant/core/issues/97248 +urllib3>=1.26.5,<2 # Constrain httplib2 to protect against GHSA-93xj-8mrv-444m # https://github.com/advisories/GHSA-93xj-8mrv-444m diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 02d528c33e2..b2954dc777b 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -61,7 +61,9 @@ CONSTRAINT_BASE = """ pycryptodome>=3.6.6 # Constrain urllib3 to ensure we deal with CVE-2020-26137 and CVE-2021-33503 -urllib3>=1.26.5 +# Temporary setting an upper bound, to prevent compat issues with urllib3>=2 +# https://github.com/home-assistant/core/issues/97248 +urllib3>=1.26.5,<2 # Constrain httplib2 to protect against GHSA-93xj-8mrv-444m # https://github.com/advisories/GHSA-93xj-8mrv-444m From 36982cea7ac9ff916ece868b5f31bc3697d0cc2a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 27 Jul 2023 11:56:45 -0500 Subject: [PATCH 23/71] Bump aiohomekit to 2.6.12 (#97342) --- 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 f859919fe07..8cc80ef864e 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.11"], + "requirements": ["aiohomekit==2.6.12"], "zeroconf": ["_hap._tcp.local.", "_hap._udp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index 35563221833..29420b03e8b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -249,7 +249,7 @@ aioguardian==2022.07.0 aioharmony==0.2.10 # homeassistant.components.homekit_controller -aiohomekit==2.6.11 +aiohomekit==2.6.12 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9f3e1c60dd1..b186f63d129 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -227,7 +227,7 @@ aioguardian==2022.07.0 aioharmony==0.2.10 # homeassistant.components.homekit_controller -aiohomekit==2.6.11 +aiohomekit==2.6.12 # homeassistant.components.emulated_hue # homeassistant.components.http From 768afeee21966f347b804eccb5415575a6f2bfaa Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 27 Jul 2023 20:34:13 +0200 Subject: [PATCH 24/71] Bumped version to 2023.8.0b1 --- 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 c965cf78528..c856cf47329 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from typing import Final APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2023 MINOR_VERSION: Final = 8 -PATCH_VERSION: Final = "0b0" +PATCH_VERSION: Final = "0b1" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0) diff --git a/pyproject.toml b/pyproject.toml index a15355dc9cf..1b621d828fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2023.8.0b0" +version = "2023.8.0b1" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 78dad22fb3bdc1072327e24b32227af32cc4bba7 Mon Sep 17 00:00:00 2001 From: Niels Perfors Date: Sun, 30 Jul 2023 18:53:26 +0200 Subject: [PATCH 25/71] Upgrade Verisure to 2.6.4 (#97278) --- homeassistant/components/verisure/coordinator.py | 16 +++------------- homeassistant/components/verisure/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 6 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/verisure/coordinator.py b/homeassistant/components/verisure/coordinator.py index 47fbde3ef20..bc3b68922b0 100644 --- a/homeassistant/components/verisure/coordinator.py +++ b/homeassistant/components/verisure/coordinator.py @@ -7,7 +7,6 @@ from time import sleep from verisure import ( Error as VerisureError, LoginError as VerisureLoginError, - ResponseError as VerisureResponseError, Session as Verisure, ) @@ -50,7 +49,7 @@ class VerisureDataUpdateCoordinator(DataUpdateCoordinator): except VerisureLoginError as ex: LOGGER.error("Could not log in to verisure, %s", ex) raise ConfigEntryAuthFailed("Credentials expired for Verisure") from ex - except VerisureResponseError as ex: + except VerisureError as ex: LOGGER.error("Could not log in to verisure, %s", ex) return False @@ -65,11 +64,9 @@ class VerisureDataUpdateCoordinator(DataUpdateCoordinator): try: await self.hass.async_add_executor_job(self.verisure.update_cookie) except VerisureLoginError as ex: - LOGGER.error("Credentials expired for Verisure, %s", ex) raise ConfigEntryAuthFailed("Credentials expired for Verisure") from ex - except VerisureResponseError as ex: - LOGGER.error("Could not log in to verisure, %s", ex) - raise ConfigEntryAuthFailed("Could not log in to verisure") from ex + except VerisureError as ex: + raise UpdateFailed("Unable to update cookie") from ex try: overview = await self.hass.async_add_executor_job( self.verisure.request, @@ -81,13 +78,6 @@ class VerisureDataUpdateCoordinator(DataUpdateCoordinator): self.verisure.smart_lock(), self.verisure.smartplugs(), ) - except VerisureResponseError as err: - LOGGER.debug("Cookie expired or service unavailable, %s", err) - overview = self._overview - try: - await self.hass.async_add_executor_job(self.verisure.update_cookie) - except VerisureResponseError as ex: - raise ConfigEntryAuthFailed("Credentials for Verisure expired.") from ex except VerisureError as err: LOGGER.error("Could not read overview, %s", err) raise UpdateFailed("Could not read overview") from err diff --git a/homeassistant/components/verisure/manifest.json b/homeassistant/components/verisure/manifest.json index 66dccdc07de..98440f67e4c 100644 --- a/homeassistant/components/verisure/manifest.json +++ b/homeassistant/components/verisure/manifest.json @@ -12,5 +12,5 @@ "integration_type": "hub", "iot_class": "cloud_polling", "loggers": ["verisure"], - "requirements": ["vsure==2.6.1"] + "requirements": ["vsure==2.6.4"] } diff --git a/requirements_all.txt b/requirements_all.txt index 29420b03e8b..f9ea0c59975 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2634,7 +2634,7 @@ volkszaehler==0.4.0 volvooncall==0.10.3 # homeassistant.components.verisure -vsure==2.6.1 +vsure==2.6.4 # homeassistant.components.vasttrafik vtjp==0.1.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b186f63d129..ea3923064eb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1934,7 +1934,7 @@ voip-utils==0.1.0 volvooncall==0.10.3 # homeassistant.components.verisure -vsure==2.6.1 +vsure==2.6.4 # homeassistant.components.vulcan vulcan-api==2.3.0 From 3beffb51034d12ab42c03ac7cd403f864d6bf665 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 27 Jul 2023 23:33:08 +0200 Subject: [PATCH 26/71] Bump reolink_aio to 0.7.5 (#97357) * bump reolink-aio to 0.7.4 * Bump reolink_aio to 0.7.5 --- homeassistant/components/reolink/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/reolink/manifest.json b/homeassistant/components/reolink/manifest.json index 00f0e0f518b..25994d56250 100644 --- a/homeassistant/components/reolink/manifest.json +++ b/homeassistant/components/reolink/manifest.json @@ -18,5 +18,5 @@ "documentation": "https://www.home-assistant.io/integrations/reolink", "iot_class": "local_push", "loggers": ["reolink_aio"], - "requirements": ["reolink-aio==0.7.3"] + "requirements": ["reolink-aio==0.7.5"] } diff --git a/requirements_all.txt b/requirements_all.txt index f9ea0c59975..7a23a68bd91 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2278,7 +2278,7 @@ renault-api==0.1.13 renson-endura-delta==1.5.0 # homeassistant.components.reolink -reolink-aio==0.7.3 +reolink-aio==0.7.5 # homeassistant.components.idteck_prox rfk101py==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ea3923064eb..0a568957800 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1674,7 +1674,7 @@ renault-api==0.1.13 renson-endura-delta==1.5.0 # homeassistant.components.reolink -reolink-aio==0.7.3 +reolink-aio==0.7.5 # homeassistant.components.rflink rflink==0.0.65 From f54c36ec16f70980f84125b1055466df8a9c7f4f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 30 Jul 2023 09:39:40 -0700 Subject: [PATCH 27/71] Bump dbus-fast to 1.87.5 (#97364) --- homeassistant/components/bluetooth/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/bluetooth/manifest.json b/homeassistant/components/bluetooth/manifest.json index cbeab2abec0..bc07e2b94ae 100644 --- a/homeassistant/components/bluetooth/manifest.json +++ b/homeassistant/components/bluetooth/manifest.json @@ -19,6 +19,6 @@ "bluetooth-adapters==0.16.0", "bluetooth-auto-recovery==1.2.1", "bluetooth-data-tools==1.6.1", - "dbus-fast==1.87.2" + "dbus-fast==1.87.5" ] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index a0046569eb8..be1dff7623d 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -16,7 +16,7 @@ bluetooth-data-tools==1.6.1 certifi>=2021.5.30 ciso8601==2.3.0 cryptography==41.0.2 -dbus-fast==1.87.2 +dbus-fast==1.87.5 fnv-hash-fast==0.4.0 ha-av==10.1.0 hass-nabucasa==0.69.0 diff --git a/requirements_all.txt b/requirements_all.txt index 7a23a68bd91..da60be35125 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -632,7 +632,7 @@ datadog==0.15.0 datapoint==0.9.8 # homeassistant.components.bluetooth -dbus-fast==1.87.2 +dbus-fast==1.87.5 # homeassistant.components.debugpy debugpy==1.6.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0a568957800..70252698d2c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -515,7 +515,7 @@ datadog==0.15.0 datapoint==0.9.8 # homeassistant.components.bluetooth -dbus-fast==1.87.2 +dbus-fast==1.87.5 # homeassistant.components.debugpy debugpy==1.6.7 From 1a0593fc9a7ef88fea8281c90b33b434a000d2b7 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Sun, 30 Jul 2023 11:42:28 -0500 Subject: [PATCH 28/71] Allow deleting config entry devices in jellyfin (#97377) --- homeassistant/components/jellyfin/__init__.py | 16 +++++ .../components/jellyfin/coordinator.py | 3 + tests/components/jellyfin/test_init.py | 60 +++++++++++++++++++ 3 files changed, 79 insertions(+) diff --git a/homeassistant/components/jellyfin/__init__.py b/homeassistant/components/jellyfin/__init__.py index 4ee97020724..f25c3410edb 100644 --- a/homeassistant/components/jellyfin/__init__.py +++ b/homeassistant/components/jellyfin/__init__.py @@ -4,6 +4,7 @@ from typing import Any from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import device_registry as dr from .client_wrapper import CannotConnect, InvalidAuth, create_client, validate_input from .const import CONF_CLIENT_DEVICE_ID, DOMAIN, LOGGER, PLATFORMS @@ -60,3 +61,18 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN].pop(entry.entry_id) return True + + +async def async_remove_config_entry_device( + hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry +) -> bool: + """Remove device from a config entry.""" + data = hass.data[DOMAIN][config_entry.entry_id] + coordinator = data.coordinators["sessions"] + + return not device_entry.identifiers.intersection( + ( + (DOMAIN, coordinator.server_id), + *((DOMAIN, id) for id in coordinator.device_ids), + ) + ) diff --git a/homeassistant/components/jellyfin/coordinator.py b/homeassistant/components/jellyfin/coordinator.py index 3d5b150f39f..f4ab98ca268 100644 --- a/homeassistant/components/jellyfin/coordinator.py +++ b/homeassistant/components/jellyfin/coordinator.py @@ -47,6 +47,7 @@ class JellyfinDataUpdateCoordinator(DataUpdateCoordinator[JellyfinDataT], ABC): self.user_id: str = user_id self.session_ids: set[str] = set() + self.device_ids: set[str] = set() async def _async_update_data(self) -> JellyfinDataT: """Get the latest data from Jellyfin.""" @@ -75,4 +76,6 @@ class SessionsDataUpdateCoordinator( and session["Client"] != USER_APP_NAME } + self.device_ids = {session["DeviceId"] for session in sessions_by_id.values()} + return sessions_by_id diff --git a/tests/components/jellyfin/test_init.py b/tests/components/jellyfin/test_init.py index 56e352bd71f..9af73391d18 100644 --- a/tests/components/jellyfin/test_init.py +++ b/tests/components/jellyfin/test_init.py @@ -4,10 +4,29 @@ from unittest.mock import MagicMock from homeassistant.components.jellyfin.const import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr +from homeassistant.setup import async_setup_component from . import async_load_json_fixture from tests.common import MockConfigEntry +from tests.typing import MockHAClientWebSocket, WebSocketGenerator + + +async def remove_device( + ws_client: MockHAClientWebSocket, device_id: str, config_entry_id: str +) -> bool: + """Remove config entry from a device.""" + await ws_client.send_json( + { + "id": 1, + "type": "config/device_registry/remove_config_entry", + "config_entry_id": config_entry_id, + "device_id": device_id, + } + ) + response = await ws_client.receive_json() + return response["success"] async def test_config_entry_not_ready( @@ -66,3 +85,44 @@ async def test_load_unload_config_entry( await hass.async_block_till_done() assert mock_config_entry.entry_id not in hass.data[DOMAIN] assert mock_config_entry.state is ConfigEntryState.NOT_LOADED + + +async def test_device_remove_devices( + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + mock_config_entry: MockConfigEntry, + mock_jellyfin: MagicMock, + device_registry: dr.DeviceRegistry, +) -> None: + """Test we can only remove a device that no longer exists.""" + assert await async_setup_component(hass, "config", {}) + + mock_config_entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + device_entry = device_registry.async_get_device( + identifiers={ + ( + DOMAIN, + "DEVICE-UUID", + ) + }, + ) + assert ( + await remove_device( + await hass_ws_client(hass), device_entry.id, mock_config_entry.entry_id + ) + is False + ) + old_device_entry = device_registry.async_get_or_create( + config_entry_id=mock_config_entry.entry_id, + identifiers={(DOMAIN, "OLD-DEVICE-UUID")}, + ) + assert ( + await remove_device( + await hass_ws_client(hass), old_device_entry.id, mock_config_entry.entry_id + ) + is True + ) From 945959827dc569c0ecaf3012c8fe359ee5d88d7a Mon Sep 17 00:00:00 2001 From: G Johansson Date: Fri, 28 Jul 2023 11:52:23 +0200 Subject: [PATCH 29/71] Bump pysensibo to 1.0.32 (#97382) --- homeassistant/components/sensibo/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensibo/manifest.json b/homeassistant/components/sensibo/manifest.json index f90b887d04c..26182102442 100644 --- a/homeassistant/components/sensibo/manifest.json +++ b/homeassistant/components/sensibo/manifest.json @@ -15,5 +15,5 @@ "iot_class": "cloud_polling", "loggers": ["pysensibo"], "quality_scale": "platinum", - "requirements": ["pysensibo==1.0.31"] + "requirements": ["pysensibo==1.0.32"] } diff --git a/requirements_all.txt b/requirements_all.txt index da60be35125..2f9a9033d28 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1982,7 +1982,7 @@ pysabnzbd==1.1.1 pysaj==0.0.16 # homeassistant.components.sensibo -pysensibo==1.0.31 +pysensibo==1.0.32 # homeassistant.components.serial # homeassistant.components.zha diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 70252698d2c..31561fd3c20 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1474,7 +1474,7 @@ pyrympro==0.0.7 pysabnzbd==1.1.1 # homeassistant.components.sensibo -pysensibo==1.0.31 +pysensibo==1.0.32 # homeassistant.components.serial # homeassistant.components.zha From 38e22f5614dbbc6ddc1ddfaa731cad478080612a Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sun, 30 Jul 2023 18:49:27 +0200 Subject: [PATCH 30/71] Regard long poll without events as valid (#97383) --- homeassistant/components/reolink/host.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/reolink/host.py b/homeassistant/components/reolink/host.py index 81fbda63fef..36b1661e1ac 100644 --- a/homeassistant/components/reolink/host.py +++ b/homeassistant/components/reolink/host.py @@ -457,7 +457,7 @@ class ReolinkHost: self._long_poll_error = False - if not self._long_poll_received and channels != []: + if not self._long_poll_received: self._long_poll_received = True ir.async_delete_issue(self._hass, DOMAIN, "webhook_url") From 7f9db4039096dc08701213a02f34b87d406ed399 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sun, 30 Jul 2023 18:47:34 +0200 Subject: [PATCH 31/71] Manual trigger entity fix name influence entity_id (#97398) --- homeassistant/components/scrape/sensor.py | 1 - homeassistant/components/sql/sensor.py | 9 ++++----- homeassistant/helpers/template_entity.py | 5 +++++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/scrape/sensor.py b/homeassistant/components/scrape/sensor.py index a68083856f7..cc4cd269606 100644 --- a/homeassistant/components/scrape/sensor.py +++ b/homeassistant/components/scrape/sensor.py @@ -157,7 +157,6 @@ class ScrapeSensor( """Initialize a web scrape sensor.""" CoordinatorEntity.__init__(self, coordinator) ManualTriggerEntity.__init__(self, hass, trigger_entity_config) - self._attr_name = trigger_entity_config[CONF_NAME].template self._attr_native_unit_of_measurement = unit_of_measurement self._attr_state_class = state_class self._select = select diff --git a/homeassistant/components/sql/sensor.py b/homeassistant/components/sql/sensor.py index 0c8e90b8895..aecc34d7009 100644 --- a/homeassistant/components/sql/sensor.py +++ b/homeassistant/components/sql/sensor.py @@ -344,16 +344,15 @@ class SQLSensor(ManualTriggerEntity, SensorEntity): self._attr_extra_state_attributes = {} self._use_database_executor = use_database_executor self._lambda_stmt = _generate_lambda_stmt(query) - self._attr_name = ( - None if not yaml else trigger_entity_config[CONF_NAME].template - ) - self._attr_has_entity_name = not yaml + if not yaml: + self._attr_name = None + self._attr_has_entity_name = True if not yaml and trigger_entity_config.get(CONF_UNIQUE_ID): self._attr_device_info = DeviceInfo( entry_type=DeviceEntryType.SERVICE, identifiers={(DOMAIN, trigger_entity_config[CONF_UNIQUE_ID])}, manufacturer="SQL", - name=trigger_entity_config[CONF_NAME].template, + name=self.name, ) async def async_added_to_hass(self) -> None: diff --git a/homeassistant/helpers/template_entity.py b/homeassistant/helpers/template_entity.py index b7be7c2c9a6..2e5cebf8571 100644 --- a/homeassistant/helpers/template_entity.py +++ b/homeassistant/helpers/template_entity.py @@ -626,6 +626,11 @@ class ManualTriggerEntity(TriggerBaseEntity): ) -> None: """Initialize the entity.""" TriggerBaseEntity.__init__(self, hass, config) + # Need initial rendering on `name` as it influence the `entity_id` + self._rendered[CONF_NAME] = config[CONF_NAME].async_render( + {}, + parse_result=CONF_NAME in self._parse_result, + ) @callback def _process_manual_data(self, value: Any | None = None) -> None: From 364e7b838a234f184522f9d19064cd21718769d2 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 30 Jul 2023 18:43:42 +0200 Subject: [PATCH 32/71] Return the actual media url from media extractor (#97408) --- homeassistant/components/media_extractor/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/media_extractor/__init__.py b/homeassistant/components/media_extractor/__init__.py index a35650f0092..d00f1b33ccc 100644 --- a/homeassistant/components/media_extractor/__init__.py +++ b/homeassistant/components/media_extractor/__init__.py @@ -127,7 +127,12 @@ class MediaExtractor: _LOGGER.error("Could not extract stream for the query: %s", query) raise MEQueryException() from err - return requested_stream["webpage_url"] + if "formats" in requested_stream: + best_stream = requested_stream["formats"][ + len(requested_stream["formats"]) - 1 + ] + return best_stream["url"] + return requested_stream["url"] return stream_selector From f1fc09cb1d4cc987353d81adb5889b910f592b91 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 28 Jul 2023 19:41:41 +0200 Subject: [PATCH 33/71] Small cleanup in event entity (#97409) --- homeassistant/components/event/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/event/__init__.py b/homeassistant/components/event/__init__.py index 98dd6036bc9..f6ba2d79bfe 100644 --- a/homeassistant/components/event/__init__.py +++ b/homeassistant/components/event/__init__.py @@ -45,7 +45,6 @@ __all__ = [ "EventDeviceClass", "EventEntity", "EventEntityDescription", - "EventEntityFeature", ] # mypy: disallow-any-generics @@ -104,7 +103,7 @@ class EventExtraStoredData(ExtraStoredData): class EventEntity(RestoreEntity): - """Representation of a Event entity.""" + """Representation of an Event entity.""" entity_description: EventEntityDescription _attr_device_class: EventDeviceClass | None From 734c16b81698093587a55cc5e73db76ce21cd1d8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 28 Jul 2023 16:37:08 -0500 Subject: [PATCH 34/71] Bump nexia to 2.0.7 (#97432) --- homeassistant/components/nexia/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/nexia/manifest.json b/homeassistant/components/nexia/manifest.json index 2e54e773a44..5464a241b7a 100644 --- a/homeassistant/components/nexia/manifest.json +++ b/homeassistant/components/nexia/manifest.json @@ -12,5 +12,5 @@ "documentation": "https://www.home-assistant.io/integrations/nexia", "iot_class": "cloud_polling", "loggers": ["nexia"], - "requirements": ["nexia==2.0.6"] + "requirements": ["nexia==2.0.7"] } diff --git a/requirements_all.txt b/requirements_all.txt index 2f9a9033d28..471246ea230 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1258,7 +1258,7 @@ nettigo-air-monitor==2.1.0 neurio==0.3.1 # homeassistant.components.nexia -nexia==2.0.6 +nexia==2.0.7 # homeassistant.components.nextcloud nextcloudmonitor==1.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 31561fd3c20..f4c318f45dd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -966,7 +966,7 @@ netmap==0.7.0.2 nettigo-air-monitor==2.1.0 # homeassistant.components.nexia -nexia==2.0.6 +nexia==2.0.7 # homeassistant.components.nextcloud nextcloudmonitor==1.4.0 From b23286ce6f16879bcc00172887e1906290d7c5a0 Mon Sep 17 00:00:00 2001 From: tronikos Date: Sun, 30 Jul 2023 09:41:14 -0700 Subject: [PATCH 35/71] Bump opower to 0.0.16 (#97437) --- homeassistant/components/opower/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/opower/manifest.json b/homeassistant/components/opower/manifest.json index 08f25d20eff..c054af8f43c 100644 --- a/homeassistant/components/opower/manifest.json +++ b/homeassistant/components/opower/manifest.json @@ -6,5 +6,5 @@ "dependencies": ["recorder"], "documentation": "https://www.home-assistant.io/integrations/opower", "iot_class": "cloud_polling", - "requirements": ["opower==0.0.15"] + "requirements": ["opower==0.0.16"] } diff --git a/requirements_all.txt b/requirements_all.txt index 471246ea230..74cc2d7f124 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1368,7 +1368,7 @@ openwrt-luci-rpc==1.1.16 openwrt-ubus-rpc==0.0.2 # homeassistant.components.opower -opower==0.0.15 +opower==0.0.16 # homeassistant.components.oralb oralb-ble==0.17.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f4c318f45dd..8c40163b0c6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1037,7 +1037,7 @@ openerz-api==0.2.0 openhomedevice==2.2.0 # homeassistant.components.opower -opower==0.0.15 +opower==0.0.16 # homeassistant.components.oralb oralb-ble==0.17.6 From 3764c2e9dea18e2105564ab6cd912d37b5804c66 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sun, 30 Jul 2023 18:49:00 +0200 Subject: [PATCH 36/71] Reolink long poll recover (#97465) --- homeassistant/components/reolink/host.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/reolink/host.py b/homeassistant/components/reolink/host.py index 36b1661e1ac..5882e5e66a4 100644 --- a/homeassistant/components/reolink/host.py +++ b/homeassistant/components/reolink/host.py @@ -267,7 +267,19 @@ class ReolinkHost: async def _async_start_long_polling(self): """Start ONVIF long polling task.""" if self._long_poll_task is None: - await self._api.subscribe(sub_type=SubType.long_poll) + try: + await self._api.subscribe(sub_type=SubType.long_poll) + except ReolinkError as err: + # make sure the long_poll_task is always created to try again later + if not self._lost_subscription: + self._lost_subscription = True + _LOGGER.error( + "Reolink %s event long polling subscription lost: %s", + self._api.nvr_name, + str(err), + ) + else: + self._lost_subscription = False self._long_poll_task = asyncio.create_task(self._async_long_polling()) async def _async_stop_long_polling(self): @@ -319,7 +331,13 @@ class ReolinkHost: try: await self._renew(SubType.push) if self._long_poll_task is not None: - await self._renew(SubType.long_poll) + if not self._api.subscribed(SubType.long_poll): + _LOGGER.debug("restarting long polling task") + # To prevent 5 minute request timeout + await self._async_stop_long_polling() + await self._async_start_long_polling() + else: + await self._renew(SubType.long_poll) except SubscriptionError as err: if not self._lost_subscription: self._lost_subscription = True From 93c536882be954c559cdc5690fce80ade5fe3f99 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 30 Jul 2023 18:40:38 +0200 Subject: [PATCH 37/71] Update ha-av to 10.1.1 (#97481) --- homeassistant/components/generic/manifest.json | 2 +- homeassistant/components/stream/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/generic/manifest.json b/homeassistant/components/generic/manifest.json index ea02bfedefb..a89ee370920 100644 --- a/homeassistant/components/generic/manifest.json +++ b/homeassistant/components/generic/manifest.json @@ -6,5 +6,5 @@ "dependencies": ["http"], "documentation": "https://www.home-assistant.io/integrations/generic", "iot_class": "local_push", - "requirements": ["ha-av==10.1.0", "Pillow==10.0.0"] + "requirements": ["ha-av==10.1.1", "Pillow==10.0.0"] } diff --git a/homeassistant/components/stream/manifest.json b/homeassistant/components/stream/manifest.json index c07a083ac52..96474ceb7eb 100644 --- a/homeassistant/components/stream/manifest.json +++ b/homeassistant/components/stream/manifest.json @@ -7,5 +7,5 @@ "integration_type": "system", "iot_class": "local_push", "quality_scale": "internal", - "requirements": ["PyTurboJPEG==1.7.1", "ha-av==10.1.0", "numpy==1.23.2"] + "requirements": ["PyTurboJPEG==1.7.1", "ha-av==10.1.1", "numpy==1.23.2"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index be1dff7623d..04c0b0fd44f 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -18,7 +18,7 @@ ciso8601==2.3.0 cryptography==41.0.2 dbus-fast==1.87.5 fnv-hash-fast==0.4.0 -ha-av==10.1.0 +ha-av==10.1.1 hass-nabucasa==0.69.0 hassil==1.2.5 home-assistant-bluetooth==1.10.2 diff --git a/requirements_all.txt b/requirements_all.txt index 74cc2d7f124..e5d8223eeaf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -937,7 +937,7 @@ h2==4.1.0 # homeassistant.components.generic # homeassistant.components.stream -ha-av==10.1.0 +ha-av==10.1.1 # homeassistant.components.ffmpeg ha-ffmpeg==3.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8c40163b0c6..978874dddfa 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -735,7 +735,7 @@ h2==4.1.0 # homeassistant.components.generic # homeassistant.components.stream -ha-av==10.1.0 +ha-av==10.1.1 # homeassistant.components.ffmpeg ha-ffmpeg==3.1.0 From 4bd4c5666d8ff9b13b981a672d76683186ef9788 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 30 Jul 2023 09:28:45 -0700 Subject: [PATCH 38/71] Revert using has_entity_name in ESPHome when `friendly_name` is not set (#97488) --- homeassistant/components/esphome/entity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/esphome/entity.py b/homeassistant/components/esphome/entity.py index 6b0a4cd6b26..b308d8dc08c 100644 --- a/homeassistant/components/esphome/entity.py +++ b/homeassistant/components/esphome/entity.py @@ -140,7 +140,6 @@ class EsphomeEntity(Entity, Generic[_InfoT, _StateT]): """Define a base esphome entity.""" _attr_should_poll = False - _attr_has_entity_name = True _static_info: _InfoT _state: _StateT _has_state: bool @@ -169,6 +168,7 @@ class EsphomeEntity(Entity, Generic[_InfoT, _StateT]): connections={(dr.CONNECTION_NETWORK_MAC, device_info.mac_address)} ) self._entry_id = entry_data.entry_id + self._attr_has_entity_name = bool(device_info.friendly_name) async def async_added_to_hass(self) -> None: """Register callbacks.""" From 99634e22bdac60e4c9cae5da14df0695f0433a76 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 30 Jul 2023 19:18:42 +0200 Subject: [PATCH 39/71] Bumped version to 2023.8.0b2 --- 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 c856cf47329..43809524d4a 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from typing import Final APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2023 MINOR_VERSION: Final = 8 -PATCH_VERSION: Final = "0b1" +PATCH_VERSION: Final = "0b2" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0) diff --git a/pyproject.toml b/pyproject.toml index 1b621d828fb..aaf057da02c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2023.8.0b1" +version = "2023.8.0b2" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 877c30c3a0d2b01ab0d5f01716128d84de6555e0 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Tue, 1 Aug 2023 03:05:01 -0500 Subject: [PATCH 40/71] Send language to Wyoming STT (#97344) --- homeassistant/components/wyoming/stt.py | 7 ++++++- tests/components/wyoming/conftest.py | 14 ++++++++++++++ tests/components/wyoming/snapshots/test_stt.ambr | 7 +++++++ tests/components/wyoming/test_stt.py | 16 ++++++++++------ 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/wyoming/stt.py b/homeassistant/components/wyoming/stt.py index 3f5487881a3..e64a2f14667 100644 --- a/homeassistant/components/wyoming/stt.py +++ b/homeassistant/components/wyoming/stt.py @@ -2,7 +2,7 @@ from collections.abc import AsyncIterable import logging -from wyoming.asr import Transcript +from wyoming.asr import Transcribe, Transcript from wyoming.audio import AudioChunk, AudioStart, AudioStop from wyoming.client import AsyncTcpClient @@ -89,6 +89,10 @@ class WyomingSttProvider(stt.SpeechToTextEntity): """Process an audio stream to STT service.""" try: async with AsyncTcpClient(self.service.host, self.service.port) as client: + # Set transcription language + await client.write_event(Transcribe(language=metadata.language).event()) + + # Begin audio stream await client.write_event( AudioStart( rate=SAMPLE_RATE, @@ -106,6 +110,7 @@ class WyomingSttProvider(stt.SpeechToTextEntity): ) await client.write_event(chunk.event()) + # End audio stream await client.write_event(AudioStop().event()) while True: diff --git a/tests/components/wyoming/conftest.py b/tests/components/wyoming/conftest.py index 0dd9041a0d5..6b4e705914f 100644 --- a/tests/components/wyoming/conftest.py +++ b/tests/components/wyoming/conftest.py @@ -4,6 +4,7 @@ from unittest.mock import AsyncMock, patch import pytest +from homeassistant.components import stt from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -69,3 +70,16 @@ async def init_wyoming_tts(hass: HomeAssistant, tts_config_entry: ConfigEntry): return_value=TTS_INFO, ): await hass.config_entries.async_setup(tts_config_entry.entry_id) + + +@pytest.fixture +def metadata(hass: HomeAssistant) -> stt.SpeechMetadata: + """Get default STT metadata.""" + return stt.SpeechMetadata( + language=hass.config.language, + format=stt.AudioFormats.WAV, + codec=stt.AudioCodecs.PCM, + bit_rate=stt.AudioBitRates.BITRATE_16, + sample_rate=stt.AudioSampleRates.SAMPLERATE_16000, + channel=stt.AudioChannels.CHANNEL_MONO, + ) diff --git a/tests/components/wyoming/snapshots/test_stt.ambr b/tests/components/wyoming/snapshots/test_stt.ambr index 08fe6a1ef8e..784f89b2ab8 100644 --- a/tests/components/wyoming/snapshots/test_stt.ambr +++ b/tests/components/wyoming/snapshots/test_stt.ambr @@ -1,6 +1,13 @@ # serializer version: 1 # name: test_streaming_audio list([ + dict({ + 'data': dict({ + 'language': 'en', + }), + 'payload': None, + 'type': 'transcibe', + }), dict({ 'data': dict({ 'channels': 1, diff --git a/tests/components/wyoming/test_stt.py b/tests/components/wyoming/test_stt.py index 021419f3a5e..1938d44d310 100644 --- a/tests/components/wyoming/test_stt.py +++ b/tests/components/wyoming/test_stt.py @@ -27,7 +27,9 @@ async def test_support(hass: HomeAssistant, init_wyoming_stt) -> None: assert entity.supported_channels == [stt.AudioChannels.CHANNEL_MONO] -async def test_streaming_audio(hass: HomeAssistant, init_wyoming_stt, snapshot) -> None: +async def test_streaming_audio( + hass: HomeAssistant, init_wyoming_stt, metadata, snapshot +) -> None: """Test streaming audio.""" entity = stt.async_get_speech_to_text_entity(hass, "stt.test_asr") assert entity is not None @@ -40,7 +42,7 @@ async def test_streaming_audio(hass: HomeAssistant, init_wyoming_stt, snapshot) "homeassistant.components.wyoming.stt.AsyncTcpClient", MockAsyncTcpClient([Transcript(text="Hello world").event()]), ) as mock_client: - result = await entity.async_process_audio_stream(None, audio_stream()) + result = await entity.async_process_audio_stream(metadata, audio_stream()) assert result.result == stt.SpeechResultState.SUCCESS assert result.text == "Hello world" @@ -48,7 +50,7 @@ async def test_streaming_audio(hass: HomeAssistant, init_wyoming_stt, snapshot) async def test_streaming_audio_connection_lost( - hass: HomeAssistant, init_wyoming_stt + hass: HomeAssistant, init_wyoming_stt, metadata ) -> None: """Test streaming audio and losing connection.""" entity = stt.async_get_speech_to_text_entity(hass, "stt.test_asr") @@ -61,13 +63,15 @@ async def test_streaming_audio_connection_lost( "homeassistant.components.wyoming.stt.AsyncTcpClient", MockAsyncTcpClient([None]), ): - result = await entity.async_process_audio_stream(None, audio_stream()) + result = await entity.async_process_audio_stream(metadata, audio_stream()) assert result.result == stt.SpeechResultState.ERROR assert result.text is None -async def test_streaming_audio_oserror(hass: HomeAssistant, init_wyoming_stt) -> None: +async def test_streaming_audio_oserror( + hass: HomeAssistant, init_wyoming_stt, metadata +) -> None: """Test streaming audio and error raising.""" entity = stt.async_get_speech_to_text_entity(hass, "stt.test_asr") assert entity is not None @@ -81,7 +85,7 @@ async def test_streaming_audio_oserror(hass: HomeAssistant, init_wyoming_stt) -> "homeassistant.components.wyoming.stt.AsyncTcpClient", mock_client, ), patch.object(mock_client, "read_event", side_effect=OSError("Boom!")): - result = await entity.async_process_audio_stream(None, audio_stream()) + result = await entity.async_process_audio_stream(metadata, audio_stream()) assert result.result == stt.SpeechResultState.ERROR assert result.text is None From da401d5ad68ed6149cdb1cafb966cfc3c923becc Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 31 Jul 2023 21:01:25 +0200 Subject: [PATCH 41/71] Bump reolink_aio to 0.7.6 + Timeout (#97464) --- homeassistant/components/reolink/__init__.py | 11 +++++------ homeassistant/components/reolink/host.py | 6 ++++-- homeassistant/components/reolink/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/reolink/__init__.py b/homeassistant/components/reolink/__init__.py index 2de87659919..88eec9780a1 100644 --- a/homeassistant/components/reolink/__init__.py +++ b/homeassistant/components/reolink/__init__.py @@ -9,6 +9,7 @@ import logging from typing import Literal import async_timeout +from reolink_aio.api import RETRY_ATTEMPTS from reolink_aio.exceptions import CredentialsInvalidError, ReolinkError from homeassistant.config_entries import ConfigEntry @@ -77,15 +78,13 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b async def async_device_config_update() -> None: """Update the host state cache and renew the ONVIF-subscription.""" - async with async_timeout.timeout(host.api.timeout): + async with async_timeout.timeout(host.api.timeout * (RETRY_ATTEMPTS + 2)): try: await host.update_states() except ReolinkError as err: - raise UpdateFailed( - f"Error updating Reolink {host.api.nvr_name}" - ) from err + raise UpdateFailed(str(err)) from err - async with async_timeout.timeout(host.api.timeout): + async with async_timeout.timeout(host.api.timeout * (RETRY_ATTEMPTS + 2)): await host.renew() async def async_check_firmware_update() -> str | Literal[False]: @@ -93,7 +92,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b if not host.api.supported(None, "update"): return False - async with async_timeout.timeout(host.api.timeout): + async with async_timeout.timeout(host.api.timeout * (RETRY_ATTEMPTS + 2)): try: return await host.api.check_new_firmware() except (ReolinkError, asyncio.exceptions.CancelledError) as err: diff --git a/homeassistant/components/reolink/host.py b/homeassistant/components/reolink/host.py index 5882e5e66a4..5a0289c38b1 100644 --- a/homeassistant/components/reolink/host.py +++ b/homeassistant/components/reolink/host.py @@ -24,7 +24,7 @@ from homeassistant.helpers.network import NoURLAvailableError, get_url from .const import CONF_PROTOCOL, CONF_USE_HTTPS, DOMAIN from .exceptions import ReolinkSetupException, ReolinkWebhookException, UserNotAdmin -DEFAULT_TIMEOUT = 60 +DEFAULT_TIMEOUT = 30 FIRST_ONVIF_TIMEOUT = 10 SUBSCRIPTION_RENEW_THRESHOLD = 300 POLL_INTERVAL_NO_PUSH = 5 @@ -469,7 +469,9 @@ class ReolinkHost: await asyncio.sleep(LONG_POLL_ERROR_COOLDOWN) continue except Exception as ex: - _LOGGER.exception("Error while requesting ONVIF pull point: %s", ex) + _LOGGER.exception( + "Unexpected exception while requesting ONVIF pull point: %s", ex + ) await self._api.unsubscribe(sub_type=SubType.long_poll) raise ex diff --git a/homeassistant/components/reolink/manifest.json b/homeassistant/components/reolink/manifest.json index 25994d56250..fa61f873cca 100644 --- a/homeassistant/components/reolink/manifest.json +++ b/homeassistant/components/reolink/manifest.json @@ -18,5 +18,5 @@ "documentation": "https://www.home-assistant.io/integrations/reolink", "iot_class": "local_push", "loggers": ["reolink_aio"], - "requirements": ["reolink-aio==0.7.5"] + "requirements": ["reolink-aio==0.7.6"] } diff --git a/requirements_all.txt b/requirements_all.txt index e5d8223eeaf..3c3bdaea46b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2278,7 +2278,7 @@ renault-api==0.1.13 renson-endura-delta==1.5.0 # homeassistant.components.reolink -reolink-aio==0.7.5 +reolink-aio==0.7.6 # homeassistant.components.idteck_prox rfk101py==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 978874dddfa..bebdfa4dfbe 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1674,7 +1674,7 @@ renault-api==0.1.13 renson-endura-delta==1.5.0 # homeassistant.components.reolink -reolink-aio==0.7.5 +reolink-aio==0.7.6 # homeassistant.components.rflink rflink==0.0.65 From c950abd32339191f1a4caeb00ab6365c0a68c9a7 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 31 Jul 2023 09:07:13 +0200 Subject: [PATCH 42/71] Delay creation of Reolink repair issues (#97476) * delay creation of repair issues * fix tests --- homeassistant/components/reolink/host.py | 37 ++++++++++++------------ tests/components/reolink/test_init.py | 11 ++++++- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/reolink/host.py b/homeassistant/components/reolink/host.py index 5a0289c38b1..9bcafb8f00d 100644 --- a/homeassistant/components/reolink/host.py +++ b/homeassistant/components/reolink/host.py @@ -26,6 +26,7 @@ from .exceptions import ReolinkSetupException, ReolinkWebhookException, UserNotA DEFAULT_TIMEOUT = 30 FIRST_ONVIF_TIMEOUT = 10 +FIRST_ONVIF_LONG_POLL_TIMEOUT = 90 SUBSCRIPTION_RENEW_THRESHOLD = 300 POLL_INTERVAL_NO_PUSH = 5 LONG_POLL_COOLDOWN = 0.75 @@ -205,7 +206,7 @@ class ReolinkHost: # ONVIF push is not received, start long polling and schedule check await self._async_start_long_polling() self._cancel_long_poll_check = async_call_later( - self._hass, FIRST_ONVIF_TIMEOUT, self._async_check_onvif_long_poll + self._hass, FIRST_ONVIF_LONG_POLL_TIMEOUT, self._async_check_onvif_long_poll ) self._cancel_onvif_check = None @@ -215,7 +216,7 @@ class ReolinkHost: if not self._long_poll_received: _LOGGER.debug( "Did not receive state through ONVIF long polling after %i seconds", - FIRST_ONVIF_TIMEOUT, + FIRST_ONVIF_LONG_POLL_TIMEOUT, ) ir.async_create_issue( self._hass, @@ -230,8 +231,24 @@ class ReolinkHost: "network_link": "https://my.home-assistant.io/redirect/network/", }, ) + if self._base_url.startswith("https"): + ir.async_create_issue( + self._hass, + DOMAIN, + "https_webhook", + is_fixable=False, + severity=ir.IssueSeverity.WARNING, + translation_key="https_webhook", + translation_placeholders={ + "base_url": self._base_url, + "network_link": "https://my.home-assistant.io/redirect/network/", + }, + ) + else: + ir.async_delete_issue(self._hass, DOMAIN, "https_webhook") else: ir.async_delete_issue(self._hass, DOMAIN, "webhook_url") + ir.async_delete_issue(self._hass, DOMAIN, "https_webhook") # If no ONVIF push or long polling state is received, start fast polling await self._async_poll_all_motion() @@ -426,22 +443,6 @@ class ReolinkHost: webhook_path = webhook.async_generate_path(event_id) self._webhook_url = f"{self._base_url}{webhook_path}" - if self._base_url.startswith("https"): - ir.async_create_issue( - self._hass, - DOMAIN, - "https_webhook", - is_fixable=False, - severity=ir.IssueSeverity.WARNING, - translation_key="https_webhook", - translation_placeholders={ - "base_url": self._base_url, - "network_link": "https://my.home-assistant.io/redirect/network/", - }, - ) - else: - ir.async_delete_issue(self._hass, DOMAIN, "https_webhook") - _LOGGER.debug("Registered webhook: %s", event_id) def unregister_webhook(self): diff --git a/tests/components/reolink/test_init.py b/tests/components/reolink/test_init.py index 1e588d5e3a1..f5f581760c1 100644 --- a/tests/components/reolink/test_init.py +++ b/tests/components/reolink/test_init.py @@ -116,7 +116,14 @@ async def test_https_repair_issue( hass, {"country": "GB", "internal_url": "https://test_homeassistant_address"} ) - assert await hass.config_entries.async_setup(config_entry.entry_id) + with patch( + "homeassistant.components.reolink.host.FIRST_ONVIF_TIMEOUT", new=0 + ), patch( + "homeassistant.components.reolink.host.FIRST_ONVIF_LONG_POLL_TIMEOUT", new=0 + ), patch( + "homeassistant.components.reolink.host.ReolinkHost._async_long_polling", + ): + assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() issue_registry = ir.async_get(hass) @@ -150,6 +157,8 @@ async def test_webhook_repair_issue( """Test repairs issue is raised when the webhook url is unreachable.""" with patch( "homeassistant.components.reolink.host.FIRST_ONVIF_TIMEOUT", new=0 + ), patch( + "homeassistant.components.reolink.host.FIRST_ONVIF_LONG_POLL_TIMEOUT", new=0 ), patch( "homeassistant.components.reolink.host.ReolinkHost._async_long_polling", ): From 278f02c86f3e1350d6144c2879229717fc272644 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Mon, 31 Jul 2023 14:21:34 +0200 Subject: [PATCH 43/71] Avoid leaking exception trace for philips_js (#97491) Avoid leaking exception trace --- homeassistant/components/philips_js/__init__.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/philips_js/__init__.py b/homeassistant/components/philips_js/__init__.py index 8ecc8a0e8c4..6f72f31ae8f 100644 --- a/homeassistant/components/philips_js/__init__.py +++ b/homeassistant/components/philips_js/__init__.py @@ -7,7 +7,12 @@ from datetime import timedelta import logging from typing import Any -from haphilipsjs import AutenticationFailure, ConnectionFailure, PhilipsTV +from haphilipsjs import ( + AutenticationFailure, + ConnectionFailure, + GeneralFailure, + PhilipsTV, +) from haphilipsjs.typing import SystemType from homeassistant.config_entries import ConfigEntry @@ -22,7 +27,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.entity import DeviceInfo -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import CONF_ALLOW_NOTIFY, CONF_SYSTEM, DOMAIN @@ -187,3 +192,5 @@ class PhilipsTVDataUpdateCoordinator(DataUpdateCoordinator[None]): pass except AutenticationFailure as exception: raise ConfigEntryAuthFailed(str(exception)) from exception + except GeneralFailure as exception: + raise UpdateFailed(str(exception)) from exception From 00c1f3d85ef5d23896bdf493d9e50b0d9f308b23 Mon Sep 17 00:00:00 2001 From: tronikos Date: Sun, 30 Jul 2023 12:27:57 -0700 Subject: [PATCH 44/71] Bump androidtvremote2==0.0.13 (#97494) --- homeassistant/components/androidtv_remote/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/androidtv_remote/manifest.json b/homeassistant/components/androidtv_remote/manifest.json index 3feddacd4e5..cb7a969379e 100644 --- a/homeassistant/components/androidtv_remote/manifest.json +++ b/homeassistant/components/androidtv_remote/manifest.json @@ -8,6 +8,6 @@ "iot_class": "local_push", "loggers": ["androidtvremote2"], "quality_scale": "platinum", - "requirements": ["androidtvremote2==0.0.12"], + "requirements": ["androidtvremote2==0.0.13"], "zeroconf": ["_androidtvremote2._tcp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index 3c3bdaea46b..fb98e1633b5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -399,7 +399,7 @@ amcrest==1.9.7 androidtv[async]==0.0.70 # homeassistant.components.androidtv_remote -androidtvremote2==0.0.12 +androidtvremote2==0.0.13 # homeassistant.components.anel_pwrctrl anel-pwrctrl-homeassistant==0.0.1.dev2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bebdfa4dfbe..cd42bdfa9ca 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -368,7 +368,7 @@ amberelectric==1.0.4 androidtv[async]==0.0.70 # homeassistant.components.androidtv_remote -androidtvremote2==0.0.12 +androidtvremote2==0.0.13 # homeassistant.components.anova anova-wifi==0.10.0 From c99bf90ec7a1f501e36c4c7d8d1d7de88a8605a0 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Tue, 1 Aug 2023 10:03:08 +0200 Subject: [PATCH 45/71] Offer work- a-round for MQTT entity names that start with the device name (#97495) Co-authored-by: SukramJ Co-authored-by: Franck Nijhof --- homeassistant/components/mqtt/client.py | 26 ++++++ homeassistant/components/mqtt/mixins.py | 42 ++++++++- homeassistant/components/mqtt/models.py | 1 + homeassistant/components/mqtt/strings.json | 16 ++++ tests/components/mqtt/test_mixins.py | 99 ++++++++++++++++++++-- 5 files changed, 173 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/mqtt/client.py b/homeassistant/components/mqtt/client.py index e8eabe887f2..07fbc0ca8c5 100644 --- a/homeassistant/components/mqtt/client.py +++ b/homeassistant/components/mqtt/client.py @@ -36,6 +36,7 @@ from homeassistant.core import ( ) from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.dispatcher import dispatcher_send +from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass from homeassistant.util import dt as dt_util @@ -64,6 +65,7 @@ from .const import ( DEFAULT_WILL, DEFAULT_WS_HEADERS, DEFAULT_WS_PATH, + DOMAIN, MQTT_CONNECTED, MQTT_DISCONNECTED, PROTOCOL_5, @@ -93,6 +95,10 @@ SUBSCRIBE_COOLDOWN = 0.1 UNSUBSCRIBE_COOLDOWN = 0.1 TIMEOUT_ACK = 10 +MQTT_ENTRIES_NAMING_BLOG_URL = ( + "https://developers.home-assistant.io/blog/2023-057-21-change-naming-mqtt-entities/" +) + SubscribePayloadType = str | bytes # Only bytes if encoding is None @@ -404,6 +410,7 @@ class MQTT: @callback def ha_started(_: Event) -> None: + self.register_naming_issues() self._ha_started.set() self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, ha_started) @@ -416,6 +423,25 @@ class MQTT: hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_stop_mqtt) ) + def register_naming_issues(self) -> None: + """Register issues with MQTT entity naming.""" + mqtt_data = get_mqtt_data(self.hass) + for issue_key, items in mqtt_data.issues.items(): + config_list = "\n".join([f"- {item}" for item in items]) + async_create_issue( + self.hass, + DOMAIN, + issue_key, + breaks_in_ha_version="2024.2.0", + is_fixable=False, + translation_key=issue_key, + translation_placeholders={ + "config": config_list, + }, + learn_more_url=MQTT_ENTRIES_NAMING_BLOG_URL, + severity=IssueSeverity.WARNING, + ) + def start( self, mqtt_data: MqttData, diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 9f0849a4d4c..70156703155 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -1014,6 +1014,7 @@ class MqttEntity( _attr_should_poll = False _default_name: str | None _entity_id_format: str + _issue_key: str | None def __init__( self, @@ -1027,6 +1028,7 @@ class MqttEntity( self._config: ConfigType = config self._attr_unique_id = config.get(CONF_UNIQUE_ID) self._sub_state: dict[str, EntitySubscription] = {} + self._discovery = discovery_data is not None # Load config self._setup_from_config(self._config) @@ -1050,6 +1052,7 @@ class MqttEntity( @final async def async_added_to_hass(self) -> None: """Subscribe to MQTT events.""" + self.collect_issues() await super().async_added_to_hass() self._prepare_subscribe_topics() await self._subscribe_topics() @@ -1122,6 +1125,7 @@ class MqttEntity( def _set_entity_name(self, config: ConfigType) -> None: """Help setting the entity name if needed.""" + self._issue_key = None entity_name: str | None | UndefinedType = config.get(CONF_NAME, UNDEFINED) # Only set _attr_name if it is needed if entity_name is not UNDEFINED: @@ -1130,6 +1134,7 @@ class MqttEntity( # Assign the default name self._attr_name = self._default_name if CONF_DEVICE in config: + device_name: str if CONF_NAME not in config[CONF_DEVICE]: _LOGGER.info( "MQTT device information always needs to include a name, got %s, " @@ -1137,14 +1142,47 @@ class MqttEntity( "name must be included in each entity's device configuration", config, ) - elif config[CONF_DEVICE][CONF_NAME] == entity_name: + elif (device_name := config[CONF_DEVICE][CONF_NAME]) == entity_name: + self._attr_name = None + self._issue_key = ( + "entity_name_is_device_name_discovery" + if self._discovery + else "entity_name_is_device_name_yaml" + ) _LOGGER.warning( "MQTT device name is equal to entity name in your config %s, " "this is not expected. Please correct your configuration. " "The entity name will be set to `null`", config, ) - self._attr_name = None + elif isinstance(entity_name, str) and entity_name.startswith(device_name): + self._attr_name = ( + new_entity_name := entity_name[len(device_name) :].lstrip() + ) + if device_name[:1].isupper(): + # Ensure a capital if the device name first char is a capital + new_entity_name = new_entity_name[:1].upper() + new_entity_name[1:] + self._issue_key = ( + "entity_name_startswith_device_name_discovery" + if self._discovery + else "entity_name_startswith_device_name_yaml" + ) + _LOGGER.warning( + "MQTT entity name starts with the device name in your config %s, " + "this is not expected. Please correct your configuration. " + "The device name prefix will be stripped off the entity name " + "and becomes '%s'", + config, + new_entity_name, + ) + + def collect_issues(self) -> None: + """Process issues for MQTT entities.""" + if self._issue_key is None: + return + mqtt_data = get_mqtt_data(self.hass) + issues = mqtt_data.issues.setdefault(self._issue_key, set()) + issues.add(self.entity_id) def _setup_common_attributes_from_config(self, config: ConfigType) -> None: """(Re)Setup the common attributes for the entity.""" diff --git a/homeassistant/components/mqtt/models.py b/homeassistant/components/mqtt/models.py index 5a966a4455c..9afa3de3f48 100644 --- a/homeassistant/components/mqtt/models.py +++ b/homeassistant/components/mqtt/models.py @@ -305,6 +305,7 @@ class MqttData: ) discovery_unsubscribe: list[CALLBACK_TYPE] = field(default_factory=list) integration_unsubscribe: dict[str, CALLBACK_TYPE] = field(default_factory=dict) + issues: dict[str, set[str]] = field(default_factory=dict) last_discovery: float = 0.0 reload_dispatchers: list[CALLBACK_TYPE] = field(default_factory=list) reload_handlers: dict[str, Callable[[], Coroutine[Any, Any, None]]] = field( diff --git a/homeassistant/components/mqtt/strings.json b/homeassistant/components/mqtt/strings.json index f314ddd47d3..55677798a08 100644 --- a/homeassistant/components/mqtt/strings.json +++ b/homeassistant/components/mqtt/strings.json @@ -7,6 +7,22 @@ "deprecation_mqtt_legacy_vacuum_discovery": { "title": "MQTT vacuum entities with legacy schema added through MQTT discovery", "description": "MQTT vacuum entities that use the legacy schema are deprecated, please adjust your devices to use the correct schema and restart Home Assistant to fix this issue." + }, + "entity_name_is_device_name_yaml": { + "title": "Manual configured MQTT entities with a name that is equal to the device name", + "description": "Some MQTT entities have an entity name equal to the device name. This is not expected. The entity name is set to `null` as a work-a-round to avoid a duplicate name. Please update your configuration and restart Home Assistant to fix this issue.\n\nList of affected entities:\n\n{config}" + }, + "entity_name_startswith_device_name_yaml": { + "title": "Manual configured MQTT entities with a name that starts with the device name", + "description": "Some MQTT entities have an entity name that starts with the device name. This is not expected. To avoid a duplicate name the device name prefix is stripped of the entity name as a work-a-round. Please update your configuration and restart Home Assistant to fix this issue. \n\nList of affected entities:\n\n{config}" + }, + "entity_name_is_device_name_discovery": { + "title": "Discovered MQTT entities with a name that is equal to the device name", + "description": "Some MQTT entities have an entity name equal to the device name. This is not expected. The entity name is set to `null` as a work-a-round to avoid a duplicate name. Please inform the maintainer of the software application that supplies the affected entities to fix this issue.\n\nList of affected entities:\n\n{config}" + }, + "entity_name_startswith_device_name_discovery": { + "title": "Discovered entities with a name that starts with the device name", + "description": "Some MQTT entities have an entity name that starts with the device name. This is not expected. To avoid a duplicate name the device name prefix is stripped of the entity name as a work-a-round. Please inform the maintainer of the software application that supplies the affected entities to fix this issue. \n\nList of affected entities:\n\n{config}" } }, "config": { diff --git a/tests/components/mqtt/test_mixins.py b/tests/components/mqtt/test_mixins.py index 23367d7829f..18269eb6970 100644 --- a/tests/components/mqtt/test_mixins.py +++ b/tests/components/mqtt/test_mixins.py @@ -6,14 +6,19 @@ import pytest from homeassistant.components import mqtt, sensor from homeassistant.components.mqtt.sensor import DEFAULT_NAME as DEFAULT_SENSOR_NAME -from homeassistant.const import EVENT_STATE_CHANGED, Platform -from homeassistant.core import HomeAssistant, callback +from homeassistant.const import ( + EVENT_HOMEASSISTANT_STARTED, + EVENT_STATE_CHANGED, + Platform, +) +from homeassistant.core import CoreState, HomeAssistant, callback from homeassistant.helpers import ( device_registry as dr, + issue_registry as ir, ) -from tests.common import async_fire_mqtt_message -from tests.typing import MqttMockHAClientGenerator +from tests.common import MockConfigEntry, async_capture_events, async_fire_mqtt_message +from tests.typing import MqttMockHAClientGenerator, MqttMockPahoClient @pytest.mark.parametrize( @@ -80,7 +85,14 @@ async def test_availability_with_shared_state_topic( @pytest.mark.parametrize( - ("hass_config", "entity_id", "friendly_name", "device_name", "assert_log"), + ( + "hass_config", + "entity_id", + "friendly_name", + "device_name", + "assert_log", + "issue_events", + ), [ ( # default_entity_name_without_device_name { @@ -96,6 +108,7 @@ async def test_availability_with_shared_state_topic( DEFAULT_SENSOR_NAME, None, True, + 0, ), ( # default_entity_name_with_device_name { @@ -111,6 +124,7 @@ async def test_availability_with_shared_state_topic( "Test MQTT Sensor", "Test", False, + 0, ), ( # name_follows_device_class { @@ -127,6 +141,7 @@ async def test_availability_with_shared_state_topic( "Test Humidity", "Test", False, + 0, ), ( # name_follows_device_class_without_device_name { @@ -143,6 +158,7 @@ async def test_availability_with_shared_state_topic( "Humidity", None, True, + 0, ), ( # name_overrides_device_class { @@ -160,6 +176,7 @@ async def test_availability_with_shared_state_topic( "Test MySensor", "Test", False, + 0, ), ( # name_set_no_device_name_set { @@ -177,6 +194,7 @@ async def test_availability_with_shared_state_topic( "MySensor", None, True, + 0, ), ( # none_entity_name_with_device_name { @@ -194,6 +212,7 @@ async def test_availability_with_shared_state_topic( "Test", "Test", False, + 0, ), ( # none_entity_name_without_device_name { @@ -211,8 +230,9 @@ async def test_availability_with_shared_state_topic( "mqtt veryunique", None, True, + 0, ), - ( # entity_name_and_device_name_the_sane + ( # entity_name_and_device_name_the_same { mqtt.DOMAIN: { sensor.DOMAIN: { @@ -231,6 +251,49 @@ async def test_availability_with_shared_state_topic( "Hello world", "Hello world", False, + 1, + ), + ( # entity_name_startswith_device_name1 + { + mqtt.DOMAIN: { + sensor.DOMAIN: { + "name": "World automation", + "state_topic": "test-topic", + "unique_id": "veryunique", + "device_class": "humidity", + "device": { + "identifiers": ["helloworld"], + "name": "World", + }, + } + } + }, + "sensor.world_automation", + "World automation", + "World", + False, + 1, + ), + ( # entity_name_startswith_device_name2 + { + mqtt.DOMAIN: { + sensor.DOMAIN: { + "name": "world automation", + "state_topic": "test-topic", + "unique_id": "veryunique", + "device_class": "humidity", + "device": { + "identifiers": ["helloworld"], + "name": "world", + }, + } + } + }, + "sensor.world_automation", + "world automation", + "world", + False, + 1, ), ], ids=[ @@ -242,24 +305,39 @@ async def test_availability_with_shared_state_topic( "name_set_no_device_name_set", "none_entity_name_with_device_name", "none_entity_name_without_device_name", - "entity_name_and_device_name_the_sane", + "entity_name_and_device_name_the_same", + "entity_name_startswith_device_name1", + "entity_name_startswith_device_name2", ], ) @patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SENSOR]) +@patch("homeassistant.components.mqtt.client.DISCOVERY_COOLDOWN", 0.0) async def test_default_entity_and_device_name( hass: HomeAssistant, - mqtt_mock_entry: MqttMockHAClientGenerator, + mqtt_client_mock: MqttMockPahoClient, + mqtt_config_entry_data, caplog: pytest.LogCaptureFixture, entity_id: str, friendly_name: str, device_name: str | None, assert_log: bool, + issue_events: int, ) -> None: """Test device name setup with and without a device_class set. This is a test helper for the _setup_common_attributes_from_config mixin. """ - await mqtt_mock_entry() + # mqtt_mock = await mqtt_mock_entry() + + events = async_capture_events(hass, ir.EVENT_REPAIRS_ISSUE_REGISTRY_UPDATED) + hass.state = CoreState.starting + await hass.async_block_till_done() + + entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "mock-broker"}) + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() registry = dr.async_get(hass) @@ -274,3 +352,6 @@ async def test_default_entity_and_device_name( assert ( "MQTT device information always needs to include a name" in caplog.text ) is assert_log + + # Assert that an issues ware registered + assert len(events) == issue_events From e473131a2c7dcfd1da95f03b91e46a2d20292c90 Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Mon, 31 Jul 2023 03:38:31 -0700 Subject: [PATCH 46/71] Bump pywemo to 1.2.0 (#97520) --- homeassistant/components/wemo/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/wemo/manifest.json b/homeassistant/components/wemo/manifest.json index bb19d2e1655..3dbd8aa32bc 100644 --- a/homeassistant/components/wemo/manifest.json +++ b/homeassistant/components/wemo/manifest.json @@ -9,7 +9,7 @@ }, "iot_class": "local_push", "loggers": ["pywemo"], - "requirements": ["pywemo==1.1.0"], + "requirements": ["pywemo==1.2.0"], "ssdp": [ { "manufacturer": "Belkin International Inc." diff --git a/requirements_all.txt b/requirements_all.txt index fb98e1633b5..b708fbe3f5f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2224,7 +2224,7 @@ pyvolumio==0.1.5 pywebpush==1.9.2 # homeassistant.components.wemo -pywemo==1.1.0 +pywemo==1.2.0 # homeassistant.components.wilight pywilight==0.0.74 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cd42bdfa9ca..e31b4513070 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1635,7 +1635,7 @@ pyvolumio==0.1.5 pywebpush==1.9.2 # homeassistant.components.wemo -pywemo==1.1.0 +pywemo==1.2.0 # homeassistant.components.wilight pywilight==0.0.74 From ec1b24f8d67321fcf4bbc16c32b11a16f2602f74 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 31 Jul 2023 12:17:51 +0200 Subject: [PATCH 47/71] Handle http error in Renault initialisation (#97530) --- homeassistant/components/renault/__init__.py | 5 ++++- tests/components/renault/test_init.py | 21 +++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/renault/__init__.py b/homeassistant/components/renault/__init__.py index b02938b1652..f69451290bc 100644 --- a/homeassistant/components/renault/__init__.py +++ b/homeassistant/components/renault/__init__.py @@ -26,7 +26,10 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b raise ConfigEntryAuthFailed() hass.data.setdefault(DOMAIN, {}) - await renault_hub.async_initialise(config_entry) + try: + await renault_hub.async_initialise(config_entry) + except aiohttp.ClientResponseError as exc: + raise ConfigEntryNotReady() from exc hass.data[DOMAIN][config_entry.entry_id] = renault_hub diff --git a/tests/components/renault/test_init.py b/tests/components/renault/test_init.py index 7f2aee9d7bd..415b07dc7e6 100644 --- a/tests/components/renault/test_init.py +++ b/tests/components/renault/test_init.py @@ -1,7 +1,7 @@ """Tests for Renault setup process.""" from collections.abc import Generator from typing import Any -from unittest.mock import patch +from unittest.mock import Mock, patch import aiohttp import pytest @@ -76,3 +76,22 @@ async def test_setup_entry_exception( assert len(hass.config_entries.async_entries(DOMAIN)) == 1 assert config_entry.state is ConfigEntryState.SETUP_RETRY assert not hass.data.get(DOMAIN) + + +@pytest.mark.usefixtures("patch_renault_account") +async def test_setup_entry_kamereon_exception( + hass: HomeAssistant, config_entry: ConfigEntry +) -> None: + """Test ConfigEntryNotReady when API raises an exception during entry setup.""" + # In this case we are testing the condition where renault_hub fails to retrieve + # list of vehicles (see Gateway Time-out on #97324). + with patch( + "renault_api.renault_client.RenaultClient.get_api_account", + side_effect=aiohttp.ClientResponseError(Mock(), (), status=504), + ): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert config_entry.state is ConfigEntryState.SETUP_RETRY + assert not hass.data.get(DOMAIN) From 83552304336c39e789f3066edf236d6e6e071a21 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 31 Jul 2023 18:44:03 +0200 Subject: [PATCH 48/71] Fix RootFolder not iterable in Radarr (#97537) Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> --- .../components/radarr/coordinator.py | 12 +- tests/components/radarr/__init__.py | 34 +++-- .../radarr/fixtures/single-movie.json | 116 ++++++++++++++++++ .../fixtures/single-rootfolder-linux.json | 6 + .../fixtures/single-rootfolder-windows.json | 6 + tests/components/radarr/test_sensor.py | 43 +++++-- 6 files changed, 189 insertions(+), 28 deletions(-) create mode 100644 tests/components/radarr/fixtures/single-movie.json create mode 100644 tests/components/radarr/fixtures/single-rootfolder-linux.json create mode 100644 tests/components/radarr/fixtures/single-rootfolder-windows.json diff --git a/homeassistant/components/radarr/coordinator.py b/homeassistant/components/radarr/coordinator.py index 5537a18725c..c318d662028 100644 --- a/homeassistant/components/radarr/coordinator.py +++ b/homeassistant/components/radarr/coordinator.py @@ -3,7 +3,7 @@ from __future__ import annotations from abc import ABC, abstractmethod from datetime import timedelta -from typing import Generic, TypeVar, cast +from typing import Generic, TypeVar from aiopyarr import Health, RadarrMovie, RootFolder, SystemStatus, exceptions from aiopyarr.models.host_configuration import PyArrHostConfiguration @@ -71,7 +71,10 @@ class DiskSpaceDataUpdateCoordinator(RadarrDataUpdateCoordinator[list[RootFolder async def _fetch_data(self) -> list[RootFolder]: """Fetch the data.""" - return cast(list[RootFolder], await self.api_client.async_get_root_folders()) + root_folders = await self.api_client.async_get_root_folders() + if isinstance(root_folders, RootFolder): + root_folders = [root_folders] + return root_folders class HealthDataUpdateCoordinator(RadarrDataUpdateCoordinator[list[Health]]): @@ -87,4 +90,7 @@ class MoviesDataUpdateCoordinator(RadarrDataUpdateCoordinator[int]): async def _fetch_data(self) -> int: """Fetch the movies data.""" - return len(cast(list[RadarrMovie], await self.api_client.async_get_movies())) + movies = await self.api_client.async_get_movies() + if isinstance(movies, RadarrMovie): + return 1 + return len(movies) diff --git a/tests/components/radarr/__init__.py b/tests/components/radarr/__init__.py index 7e574b1e3e0..069eeabe8d8 100644 --- a/tests/components/radarr/__init__.py +++ b/tests/components/radarr/__init__.py @@ -41,6 +41,7 @@ def mock_connection( error: bool = False, invalid_auth: bool = False, windows: bool = False, + single_return: bool = False, ) -> None: """Mock radarr connection.""" if error: @@ -75,22 +76,27 @@ def mock_connection( headers={"Content-Type": CONTENT_TYPE_JSON}, ) + root_folder_fixture = "rootfolder-linux" + if windows: - aioclient_mock.get( - f"{url}/api/v3/rootfolder", - text=load_fixture("radarr/rootfolder-windows.json"), - headers={"Content-Type": CONTENT_TYPE_JSON}, - ) - else: - aioclient_mock.get( - f"{url}/api/v3/rootfolder", - text=load_fixture("radarr/rootfolder-linux.json"), - headers={"Content-Type": CONTENT_TYPE_JSON}, - ) + root_folder_fixture = "rootfolder-windows" + + if single_return: + root_folder_fixture = f"single-{root_folder_fixture}" + + aioclient_mock.get( + f"{url}/api/v3/rootfolder", + text=load_fixture(f"radarr/{root_folder_fixture}.json"), + headers={"Content-Type": CONTENT_TYPE_JSON}, + ) + + movie_fixture = "movie" + if single_return: + movie_fixture = f"single-{movie_fixture}" aioclient_mock.get( f"{url}/api/v3/movie", - text=load_fixture("radarr/movie.json"), + text=load_fixture(f"radarr/{movie_fixture}.json"), headers={"Content-Type": CONTENT_TYPE_JSON}, ) @@ -139,6 +145,7 @@ async def setup_integration( connection_error: bool = False, invalid_auth: bool = False, windows: bool = False, + single_return: bool = False, ) -> MockConfigEntry: """Set up the radarr integration in Home Assistant.""" entry = MockConfigEntry( @@ -159,6 +166,7 @@ async def setup_integration( error=connection_error, invalid_auth=invalid_auth, windows=windows, + single_return=single_return, ) if not skip_entry_setup: @@ -183,7 +191,7 @@ def patch_radarr(): def create_entry(hass: HomeAssistant) -> MockConfigEntry: - """Create Efergy entry in Home Assistant.""" + """Create Radarr entry in Home Assistant.""" entry = MockConfigEntry( domain=DOMAIN, data={ diff --git a/tests/components/radarr/fixtures/single-movie.json b/tests/components/radarr/fixtures/single-movie.json new file mode 100644 index 00000000000..db9e720d285 --- /dev/null +++ b/tests/components/radarr/fixtures/single-movie.json @@ -0,0 +1,116 @@ +{ + "id": 0, + "title": "string", + "originalTitle": "string", + "alternateTitles": [ + { + "sourceType": "tmdb", + "movieId": 1, + "title": "string", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1 + } + ], + "sortTitle": "string", + "sizeOnDisk": 0, + "overview": "string", + "inCinemas": "2020-11-06T00:00:00Z", + "physicalRelease": "2019-03-19T00:00:00Z", + "images": [ + { + "coverType": "poster", + "url": "string", + "remoteUrl": "string" + } + ], + "website": "string", + "year": 0, + "hasFile": true, + "youTubeTrailerId": "string", + "studio": "string", + "path": "string", + "rootFolderPath": "string", + "qualityProfileId": 0, + "monitored": true, + "minimumAvailability": "announced", + "isAvailable": true, + "folderName": "string", + "runtime": 0, + "cleanTitle": "string", + "imdbId": "string", + "tmdbId": 0, + "titleSlug": "string", + "certification": "string", + "genres": ["string"], + "tags": [0], + "added": "2018-12-28T05:56:49Z", + "ratings": { + "votes": 0, + "value": 0 + }, + "movieFile": { + "movieId": 0, + "relativePath": "string", + "path": "string", + "size": 916662234, + "dateAdded": "2020-11-26T02:00:35Z", + "indexerFlags": 1, + "quality": { + "quality": { + "id": 14, + "name": "WEBRip-720p", + "source": "webrip", + "resolution": 720, + "modifier": "none" + }, + "revision": { + "version": 1, + "real": 0, + "isRepack": false + } + }, + "mediaInfo": { + "audioBitrate": 0, + "audioChannels": 2, + "audioCodec": "AAC", + "audioLanguages": "", + "audioStreamCount": 1, + "videoBitDepth": 8, + "videoBitrate": 1000000, + "videoCodec": "x264", + "videoFps": 25.0, + "resolution": "1280x534", + "runTime": "1:49:06", + "scanType": "Progressive", + "subtitles": "" + }, + "originalFilePath": "string", + "qualityCutoffNotMet": true, + "languages": [ + { + "id": 26, + "name": "Hindi" + } + ], + "edition": "", + "id": 35361 + }, + "collection": { + "name": "string", + "tmdbId": 0, + "images": [ + { + "coverType": "poster", + "url": "string", + "remoteUrl": "string" + } + ] + }, + "status": "deleted" +} diff --git a/tests/components/radarr/fixtures/single-rootfolder-linux.json b/tests/components/radarr/fixtures/single-rootfolder-linux.json new file mode 100644 index 00000000000..085467fda6a --- /dev/null +++ b/tests/components/radarr/fixtures/single-rootfolder-linux.json @@ -0,0 +1,6 @@ +{ + "path": "/downloads", + "freeSpace": 282500064232, + "unmappedFolders": [], + "id": 1 +} diff --git a/tests/components/radarr/fixtures/single-rootfolder-windows.json b/tests/components/radarr/fixtures/single-rootfolder-windows.json new file mode 100644 index 00000000000..25a93baa10d --- /dev/null +++ b/tests/components/radarr/fixtures/single-rootfolder-windows.json @@ -0,0 +1,6 @@ +{ + "path": "D:\\Downloads\\TV", + "freeSpace": 282500064232, + "unmappedFolders": [], + "id": 1 +} diff --git a/tests/components/radarr/test_sensor.py b/tests/components/radarr/test_sensor.py index d3dde74dcbf..f4f863d9bb6 100644 --- a/tests/components/radarr/test_sensor.py +++ b/tests/components/radarr/test_sensor.py @@ -1,4 +1,5 @@ """The tests for Radarr sensor platform.""" +import pytest from homeassistant.components.sensor import SensorDeviceClass from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_UNIT_OF_MEASUREMENT @@ -9,15 +10,43 @@ from . import setup_integration from tests.test_util.aiohttp import AiohttpClientMocker +@pytest.mark.parametrize( + ("windows", "single", "root_folder"), + [ + ( + False, + False, + "downloads", + ), + ( + False, + True, + "downloads", + ), + ( + True, + False, + "tv", + ), + ( + True, + True, + "tv", + ), + ], +) async def test_sensors( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, entity_registry_enabled_by_default: None, + windows: bool, + single: bool, + root_folder: str, ) -> None: """Test for successfully setting up the Radarr platform.""" - await setup_integration(hass, aioclient_mock) + await setup_integration(hass, aioclient_mock, windows=windows, single_return=single) - state = hass.states.get("sensor.mock_title_disk_space_downloads") + state = hass.states.get(f"sensor.mock_title_disk_space_{root_folder}") assert state.state == "263.10" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "GB" state = hass.states.get("sensor.mock_title_movies") @@ -26,13 +55,3 @@ async def test_sensors( state = hass.states.get("sensor.mock_title_start_time") assert state.state == "2020-09-01T23:50:20+00:00" assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TIMESTAMP - - -async def test_windows( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker -) -> None: - """Test for successfully setting up the Radarr platform on Windows.""" - await setup_integration(hass, aioclient_mock, windows=True) - - state = hass.states.get("sensor.mock_title_disk_space_tv") - assert state.state == "263.10" From 3f22c74ffa26921d81636d6c86594dc3a39a3a03 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Mon, 31 Jul 2023 21:16:58 +0200 Subject: [PATCH 49/71] Fix unit tests for wake_on_lan (#97542) --- tests/components/wake_on_lan/conftest.py | 19 ++++- tests/components/wake_on_lan/test_switch.py | 89 ++++++++++----------- 2 files changed, 60 insertions(+), 48 deletions(-) diff --git a/tests/components/wake_on_lan/conftest.py b/tests/components/wake_on_lan/conftest.py index 582698e39d5..5fa44f10c2c 100644 --- a/tests/components/wake_on_lan/conftest.py +++ b/tests/components/wake_on_lan/conftest.py @@ -1,7 +1,8 @@ """Test fixtures for Wake on Lan.""" from __future__ import annotations -from unittest.mock import AsyncMock, patch +from collections.abc import Generator +from unittest.mock import AsyncMock, MagicMock, patch import pytest @@ -11,3 +12,19 @@ def mock_send_magic_packet() -> AsyncMock: """Mock magic packet.""" with patch("wakeonlan.send_magic_packet") as mock_send: yield mock_send + + +@pytest.fixture +def subprocess_call_return_value() -> int | None: + """Return value for subprocess.""" + return 1 + + +@pytest.fixture(autouse=True) +def mock_subprocess_call( + subprocess_call_return_value: int, +) -> Generator[None, None, MagicMock]: + """Mock magic packet.""" + with patch("homeassistant.components.wake_on_lan.switch.sp.call") as mock_sp: + mock_sp.return_value = subprocess_call_return_value + yield mock_sp diff --git a/tests/components/wake_on_lan/test_switch.py b/tests/components/wake_on_lan/test_switch.py index 8a7fe185662..b2702ed1815 100644 --- a/tests/components/wake_on_lan/test_switch.py +++ b/tests/components/wake_on_lan/test_switch.py @@ -1,7 +1,6 @@ """The tests for the wake on lan switch platform.""" from __future__ import annotations -import subprocess from unittest.mock import AsyncMock, patch from homeassistant.components import switch @@ -38,7 +37,7 @@ async def test_valid_hostname( state = hass.states.get("switch.wake_on_lan") assert state.state == STATE_OFF - with patch.object(subprocess, "call", return_value=0): + with patch("homeassistant.components.wake_on_lan.switch.sp.call", return_value=0): await hass.services.async_call( switch.DOMAIN, SERVICE_TURN_ON, @@ -85,17 +84,16 @@ async def test_broadcast_config_ip_and_port( state = hass.states.get("switch.wake_on_lan") assert state.state == STATE_OFF - with patch.object(subprocess, "call", return_value=0): - await hass.services.async_call( - switch.DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "switch.wake_on_lan"}, - blocking=True, - ) + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.wake_on_lan"}, + blocking=True, + ) - mock_send_magic_packet.assert_called_with( - mac, ip_address=broadcast_address, port=port - ) + mock_send_magic_packet.assert_called_with( + mac, ip_address=broadcast_address, port=port + ) async def test_broadcast_config_ip( @@ -122,15 +120,14 @@ async def test_broadcast_config_ip( state = hass.states.get("switch.wake_on_lan") assert state.state == STATE_OFF - with patch.object(subprocess, "call", return_value=0): - await hass.services.async_call( - switch.DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "switch.wake_on_lan"}, - blocking=True, - ) + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.wake_on_lan"}, + blocking=True, + ) - mock_send_magic_packet.assert_called_with(mac, ip_address=broadcast_address) + mock_send_magic_packet.assert_called_with(mac, ip_address=broadcast_address) async def test_broadcast_config_port( @@ -151,15 +148,14 @@ async def test_broadcast_config_port( state = hass.states.get("switch.wake_on_lan") assert state.state == STATE_OFF - with patch.object(subprocess, "call", return_value=0): - await hass.services.async_call( - switch.DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "switch.wake_on_lan"}, - blocking=True, - ) + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.wake_on_lan"}, + blocking=True, + ) - mock_send_magic_packet.assert_called_with(mac, port=port) + mock_send_magic_packet.assert_called_with(mac, port=port) async def test_off_script( @@ -185,7 +181,7 @@ async def test_off_script( state = hass.states.get("switch.wake_on_lan") assert state.state == STATE_OFF - with patch.object(subprocess, "call", return_value=0): + with patch("homeassistant.components.wake_on_lan.switch.sp.call", return_value=0): await hass.services.async_call( switch.DOMAIN, SERVICE_TURN_ON, @@ -197,7 +193,7 @@ async def test_off_script( assert state.state == STATE_ON assert len(calls) == 0 - with patch.object(subprocess, "call", return_value=2): + with patch("homeassistant.components.wake_on_lan.switch.sp.call", return_value=1): await hass.services.async_call( switch.DOMAIN, SERVICE_TURN_OFF, @@ -230,23 +226,22 @@ async def test_no_hostname_state( state = hass.states.get("switch.wake_on_lan") assert state.state == STATE_OFF - with patch.object(subprocess, "call", return_value=0): - await hass.services.async_call( - switch.DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "switch.wake_on_lan"}, - blocking=True, - ) + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.wake_on_lan"}, + blocking=True, + ) - state = hass.states.get("switch.wake_on_lan") - assert state.state == STATE_ON + state = hass.states.get("switch.wake_on_lan") + assert state.state == STATE_ON - await hass.services.async_call( - switch.DOMAIN, - SERVICE_TURN_OFF, - {ATTR_ENTITY_ID: "switch.wake_on_lan"}, - blocking=True, - ) + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "switch.wake_on_lan"}, + blocking=True, + ) - state = hass.states.get("switch.wake_on_lan") - assert state.state == STATE_OFF + state = hass.states.get("switch.wake_on_lan") + assert state.state == STATE_OFF From d891f1a5eb01c786570272561a70f26fb03b3329 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 31 Jul 2023 21:49:20 -1000 Subject: [PATCH 50/71] Bump HAP-python to 4.7.1 (#97545) --- homeassistant/components/homekit/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homekit/manifest.json b/homeassistant/components/homekit/manifest.json index 19fd0b518b2..04ba4cc1a6a 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.7.0", + "HAP-python==4.7.1", "fnv-hash-fast==0.4.0", "PyQRCode==1.2.1", "base36==0.1.1" diff --git a/requirements_all.txt b/requirements_all.txt index b708fbe3f5f..9e51bab7254 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -26,7 +26,7 @@ CO2Signal==0.4.2 DoorBirdPy==2.1.0 # homeassistant.components.homekit -HAP-python==4.7.0 +HAP-python==4.7.1 # homeassistant.components.tasmota HATasmota==0.6.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e31b4513070..28d677a795e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -25,7 +25,7 @@ CO2Signal==0.4.2 DoorBirdPy==2.1.0 # homeassistant.components.homekit -HAP-python==4.7.0 +HAP-python==4.7.1 # homeassistant.components.tasmota HATasmota==0.6.5 From c412cf9a5e15fdab567ebecebf1d76bb5a610926 Mon Sep 17 00:00:00 2001 From: tronikos Date: Tue, 1 Aug 2023 00:45:17 -0700 Subject: [PATCH 51/71] Bump opower to 0.0.18 (#97548) --- homeassistant/components/opower/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/opower/manifest.json b/homeassistant/components/opower/manifest.json index c054af8f43c..c0eb319c10c 100644 --- a/homeassistant/components/opower/manifest.json +++ b/homeassistant/components/opower/manifest.json @@ -6,5 +6,5 @@ "dependencies": ["recorder"], "documentation": "https://www.home-assistant.io/integrations/opower", "iot_class": "cloud_polling", - "requirements": ["opower==0.0.16"] + "requirements": ["opower==0.0.18"] } diff --git a/requirements_all.txt b/requirements_all.txt index 9e51bab7254..ae8111ec0eb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1368,7 +1368,7 @@ openwrt-luci-rpc==1.1.16 openwrt-ubus-rpc==0.0.2 # homeassistant.components.opower -opower==0.0.16 +opower==0.0.18 # homeassistant.components.oralb oralb-ble==0.17.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 28d677a795e..5f2d89c544e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1037,7 +1037,7 @@ openerz-api==0.2.0 openhomedevice==2.2.0 # homeassistant.components.opower -opower==0.0.16 +opower==0.0.18 # homeassistant.components.oralb oralb-ble==0.17.6 From c600d07a9db6c542c898f4edc3cd31c69b23b430 Mon Sep 17 00:00:00 2001 From: Phil Bruckner Date: Tue, 1 Aug 2023 03:04:04 -0500 Subject: [PATCH 52/71] Bump life360 package to 6.0.0 (#97549) --- homeassistant/components/life360/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/life360/manifest.json b/homeassistant/components/life360/manifest.json index bfecce8d3ed..18b83013d70 100644 --- a/homeassistant/components/life360/manifest.json +++ b/homeassistant/components/life360/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/life360", "iot_class": "cloud_polling", "loggers": ["life360"], - "requirements": ["life360==5.5.0"] + "requirements": ["life360==6.0.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index ae8111ec0eb..ba56ae548e6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1129,7 +1129,7 @@ librouteros==3.2.0 libsoundtouch==0.8 # homeassistant.components.life360 -life360==5.5.0 +life360==6.0.0 # homeassistant.components.osramlightify lightify==1.0.7.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5f2d89c544e..78f01c83e39 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -879,7 +879,7 @@ librouteros==3.2.0 libsoundtouch==0.8 # homeassistant.components.life360 -life360==5.5.0 +life360==6.0.0 # homeassistant.components.logi_circle logi-circle==0.2.3 From f780397c2d4bcd384dec680f5f390be610342e22 Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Tue, 1 Aug 2023 01:08:08 -0700 Subject: [PATCH 53/71] Bump pywemo to 1.2.1 (#97550) --- homeassistant/components/wemo/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/wemo/manifest.json b/homeassistant/components/wemo/manifest.json index 3dbd8aa32bc..cb189116eeb 100644 --- a/homeassistant/components/wemo/manifest.json +++ b/homeassistant/components/wemo/manifest.json @@ -9,7 +9,7 @@ }, "iot_class": "local_push", "loggers": ["pywemo"], - "requirements": ["pywemo==1.2.0"], + "requirements": ["pywemo==1.2.1"], "ssdp": [ { "manufacturer": "Belkin International Inc." diff --git a/requirements_all.txt b/requirements_all.txt index ba56ae548e6..c61ebf9527b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2224,7 +2224,7 @@ pyvolumio==0.1.5 pywebpush==1.9.2 # homeassistant.components.wemo -pywemo==1.2.0 +pywemo==1.2.1 # homeassistant.components.wilight pywilight==0.0.74 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 78f01c83e39..3af0fd1fa42 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1635,7 +1635,7 @@ pyvolumio==0.1.5 pywebpush==1.9.2 # homeassistant.components.wemo -pywemo==1.2.0 +pywemo==1.2.1 # homeassistant.components.wilight pywilight==0.0.74 From 20cf5f0f2cb84737ef69f21a5d908a2c65ee0a21 Mon Sep 17 00:00:00 2001 From: Jack Boswell Date: Tue, 1 Aug 2023 20:06:19 +1200 Subject: [PATCH 54/71] Fix Starlink ping drop rate reporting (#97555) --- homeassistant/components/starlink/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/starlink/sensor.py b/homeassistant/components/starlink/sensor.py index efcf92600b8..ab76a8dffdd 100644 --- a/homeassistant/components/starlink/sensor.py +++ b/homeassistant/components/starlink/sensor.py @@ -130,6 +130,6 @@ SENSORS: tuple[StarlinkSensorEntityDescription, ...] = ( translation_key="ping_drop_rate", state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=PERCENTAGE, - value_fn=lambda data: data.status["pop_ping_drop_rate"], + value_fn=lambda data: data.status["pop_ping_drop_rate"] * 100, ), ) From dfd5c74de06837340e743fa0a42d39db5513ccdc Mon Sep 17 00:00:00 2001 From: Pedro Lamas Date: Tue, 1 Aug 2023 10:04:30 +0100 Subject: [PATCH 55/71] Fixes London Air parsing error (#97557) --- homeassistant/components/london_air/sensor.py | 10 ++++++---- tests/fixtures/london_air.json | 8 ++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/london_air/sensor.py b/homeassistant/components/london_air/sensor.py index e970f040b5f..98cc4c4b4e8 100644 --- a/homeassistant/components/london_air/sensor.py +++ b/homeassistant/components/london_air/sensor.py @@ -218,10 +218,12 @@ def parse_api_response(response): for authority in AUTHORITIES: for entry in response["HourlyAirQualityIndex"]["LocalAuthority"]: if entry["@LocalAuthorityName"] == authority: - if isinstance(entry["Site"], dict): - entry_sites_data = [entry["Site"]] - else: - entry_sites_data = entry["Site"] + entry_sites_data = [] + if "Site" in entry: + if isinstance(entry["Site"], dict): + entry_sites_data = [entry["Site"]] + else: + entry_sites_data = entry["Site"] data[authority] = parse_site(entry_sites_data) diff --git a/tests/fixtures/london_air.json b/tests/fixtures/london_air.json index 3a3d9afb643..7045a90e6e9 100644 --- a/tests/fixtures/london_air.json +++ b/tests/fixtures/london_air.json @@ -3,6 +3,14 @@ "@GroupName": "London", "@TimeToLive": "38", "LocalAuthority": [ + { + "@LocalAuthorityCode": "7", + "@LocalAuthorityName": "City of London", + "@LaCentreLatitude": "51.51333", + "@LaCentreLongitude": "-0.088947", + "@LaCentreLatitudeWGS84": "6712603.132989", + "@LaCentreLongitudeWGS84": "-9901.534748" + }, { "@LocalAuthorityCode": "24", "@LocalAuthorityName": "Merton", From 8261a769a53559c6af655aaf29073e92fcf95ec6 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 1 Aug 2023 11:46:37 +0200 Subject: [PATCH 56/71] Update frontend to 20230801.0 (#97561) --- 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 47e742bdb76..2210a44039e 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==20230725.0"] + "requirements": ["home-assistant-frontend==20230801.0"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 04c0b0fd44f..3a887804470 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -22,7 +22,7 @@ ha-av==10.1.1 hass-nabucasa==0.69.0 hassil==1.2.5 home-assistant-bluetooth==1.10.2 -home-assistant-frontend==20230725.0 +home-assistant-frontend==20230801.0 home-assistant-intents==2023.7.25 httpx==0.24.1 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index c61ebf9527b..7ff7d9b82f5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -988,7 +988,7 @@ hole==0.8.0 holidays==0.28 # homeassistant.components.frontend -home-assistant-frontend==20230725.0 +home-assistant-frontend==20230801.0 # homeassistant.components.conversation home-assistant-intents==2023.7.25 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3af0fd1fa42..23df6a82bcd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -774,7 +774,7 @@ hole==0.8.0 holidays==0.28 # homeassistant.components.frontend -home-assistant-frontend==20230725.0 +home-assistant-frontend==20230801.0 # homeassistant.components.conversation home-assistant-intents==2023.7.25 From 2f6aea450ebeb0d949dd23d0b7c8012206edbc55 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 1 Aug 2023 11:48:10 +0200 Subject: [PATCH 57/71] Bumped version to 2023.8.0b3 --- 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 43809524d4a..936393cc8cb 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from typing import Final APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2023 MINOR_VERSION: Final = 8 -PATCH_VERSION: Final = "0b2" +PATCH_VERSION: Final = "0b3" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0) diff --git a/pyproject.toml b/pyproject.toml index aaf057da02c..a47d883d146 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2023.8.0b2" +version = "2023.8.0b3" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 87c11ca419c0e0dfdf393e571f927c7440746137 Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Tue, 1 Aug 2023 14:39:31 +0200 Subject: [PATCH 58/71] Bump pyduotecno to 2023.8.0 (beta fix) (#97564) * Bump pyduotecno to 2023.7.4 * Bump to 2023.8.0 --- homeassistant/components/duotecno/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/duotecno/manifest.json b/homeassistant/components/duotecno/manifest.json index a630a3dedbd..3089e3b515b 100644 --- a/homeassistant/components/duotecno/manifest.json +++ b/homeassistant/components/duotecno/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/duotecno", "iot_class": "local_push", - "requirements": ["pyduotecno==2023.7.3"] + "requirements": ["pyduotecno==2023.8.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 7ff7d9b82f5..aed6674d810 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1650,7 +1650,7 @@ pydrawise==2023.7.1 pydroid-ipcam==2.0.0 # homeassistant.components.duotecno -pyduotecno==2023.7.3 +pyduotecno==2023.8.0 # homeassistant.components.ebox pyebox==1.1.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 23df6a82bcd..64118d5a30b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1223,7 +1223,7 @@ pydiscovergy==2.0.1 pydroid-ipcam==2.0.0 # homeassistant.components.duotecno -pyduotecno==2023.7.3 +pyduotecno==2023.8.0 # homeassistant.components.econet pyeconet==0.1.20 From 116b0267687ff6b43c718d64f2c731f58b645292 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 1 Aug 2023 21:31:45 +0200 Subject: [PATCH 59/71] Unignore today's collection for Rova (#97567) --- homeassistant/components/rova/sensor.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/rova/sensor.py b/homeassistant/components/rova/sensor.py index 21effd3da3a..f68ffbd0eaf 100644 --- a/homeassistant/components/rova/sensor.py +++ b/homeassistant/components/rova/sensor.py @@ -20,7 +20,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util import Throttle -from homeassistant.util.dt import get_time_zone, now +from homeassistant.util.dt import get_time_zone # Config for rova requests. CONF_ZIP_CODE = "zip_code" @@ -150,8 +150,7 @@ class RovaData: tzinfo=get_time_zone("Europe/Amsterdam") ) code = item["GarbageTypeCode"].lower() - - if code not in self.data and date > now(): + if code not in self.data: self.data[code] = date _LOGGER.debug("Updated Rova calendar: %s", self.data) From 2b26e205288a5254c11c659fc772f6f4aca41510 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 1 Aug 2023 09:08:12 -1000 Subject: [PATCH 60/71] Use legacy rules for ESPHome entity_id construction if `friendly_name` is unset (#97578) --- homeassistant/components/esphome/entity.py | 23 ++++++++++++++++++---- tests/components/esphome/test_entity.py | 2 +- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/esphome/entity.py b/homeassistant/components/esphome/entity.py index b308d8dc08c..c35b4dc9b13 100644 --- a/homeassistant/components/esphome/entity.py +++ b/homeassistant/components/esphome/entity.py @@ -161,14 +161,29 @@ class EsphomeEntity(Entity, Generic[_InfoT, _StateT]): assert entry_data.device_info is not None device_info = entry_data.device_info self._device_info = device_info - if object_id := entity_info.object_id: - # Use the object_id to suggest the entity_id - self.entity_id = f"{domain}.{device_info.name}_{object_id}" self._attr_device_info = DeviceInfo( connections={(dr.CONNECTION_NETWORK_MAC, device_info.mac_address)} ) self._entry_id = entry_data.entry_id - self._attr_has_entity_name = bool(device_info.friendly_name) + # + # If `friendly_name` is set, we use the Friendly naming rules, if + # `friendly_name` is not set we make an exception to the naming rules for + # backwards compatibility and use the Legacy naming rules. + # + # Friendly naming + # - Friendly name is prepended to entity names + # - Device Name is prepended to entity ids + # - Entity id is constructed from device name and object id + # + # Legacy naming + # - Device name is not prepended to entity names + # - Device name is not prepended to entity ids + # - Entity id is constructed from entity name + # + if not device_info.friendly_name: + return + self._attr_has_entity_name = True + self.entity_id = f"{domain}.{device_info.name}_{entity_info.object_id}" async def async_added_to_hass(self) -> None: """Register callbacks.""" diff --git a/tests/components/esphome/test_entity.py b/tests/components/esphome/test_entity.py index e55d4583275..ac121a93eff 100644 --- a/tests/components/esphome/test_entity.py +++ b/tests/components/esphome/test_entity.py @@ -216,6 +216,6 @@ async def test_esphome_device_without_friendly_name( states=states, device_info={"friendly_name": None}, ) - state = hass.states.get("binary_sensor.test_mybinary_sensor") + state = hass.states.get("binary_sensor.my_binary_sensor") assert state is not None assert state.state == STATE_ON From c3bcffdce7242d146a217ac1ceea5091dc167b85 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 1 Aug 2023 21:18:33 +0200 Subject: [PATCH 61/71] Fix UniFi image platform failing to setup on read-only account (#97580) --- homeassistant/components/unifi/image.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/homeassistant/components/unifi/image.py b/homeassistant/components/unifi/image.py index 25c368880fa..dc4fb93eded 100644 --- a/homeassistant/components/unifi/image.py +++ b/homeassistant/components/unifi/image.py @@ -83,6 +83,10 @@ async def async_setup_entry( ) -> None: """Set up image platform for UniFi Network integration.""" controller: UniFiController = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + + if controller.site_role != "admin": + return + controller.register_platform_add_entities( UnifiImageEntity, ENTITY_DESCRIPTIONS, async_add_entities ) From 97e28acfc934af55867ed1ae4cd1a7c2d0c6e909 Mon Sep 17 00:00:00 2001 From: TheJulianJES Date: Tue, 1 Aug 2023 22:26:36 +0200 Subject: [PATCH 62/71] Bump zha-quirks to 0.0.102 (#97588) --- homeassistant/components/zha/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index 7694a85b8ed..5e33377ec0e 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -23,7 +23,7 @@ "bellows==0.35.8", "pyserial==3.5", "pyserial-asyncio==0.6", - "zha-quirks==0.0.101", + "zha-quirks==0.0.102", "zigpy-deconz==0.21.0", "zigpy==0.56.2", "zigpy-xbee==0.18.1", diff --git a/requirements_all.txt b/requirements_all.txt index aed6674d810..8d52c7a3927 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2755,7 +2755,7 @@ zeroconf==0.71.4 zeversolar==0.3.1 # homeassistant.components.zha -zha-quirks==0.0.101 +zha-quirks==0.0.102 # homeassistant.components.zhong_hong zhong-hong-hvac==1.0.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 64118d5a30b..2a7ec674fc3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2028,7 +2028,7 @@ zeroconf==0.71.4 zeversolar==0.3.1 # homeassistant.components.zha -zha-quirks==0.0.101 +zha-quirks==0.0.102 # homeassistant.components.zha zigpy-deconz==0.21.0 From 80e0bcfaea7976a7ddda3a34ce4af69e962e3377 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 1 Aug 2023 23:15:31 +0200 Subject: [PATCH 63/71] Ensure load the device registry if it contains invalid configuration URLs (#97589) --- homeassistant/helpers/device_registry.py | 9 ++++-- tests/helpers/test_device_registry.py | 41 ++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 5764f65957e..4dd9233c6ab 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -198,9 +198,7 @@ class DeviceEntry: area_id: str | None = attr.ib(default=None) config_entries: set[str] = attr.ib(converter=set, factory=set) - configuration_url: str | URL | None = attr.ib( - converter=_validate_configuration_url, default=None - ) + configuration_url: str | None = attr.ib(default=None) connections: set[tuple[str, str]] = attr.ib(converter=set, factory=set) disabled_by: DeviceEntryDisabler | None = attr.ib(default=None) entry_type: DeviceEntryType | None = attr.ib(default=None) @@ -482,6 +480,8 @@ class DeviceRegistry: via_device: tuple[str, str] | None | UndefinedType = UNDEFINED, ) -> DeviceEntry: """Get device. Create if it doesn't exist.""" + if configuration_url is not UNDEFINED: + configuration_url = _validate_configuration_url(configuration_url) # Reconstruct a DeviceInfo dict from the arguments. # When we upgrade to Python 3.12, we can change this method to instead @@ -681,6 +681,9 @@ class DeviceRegistry: new_values["identifiers"] = new_identifiers old_values["identifiers"] = old.identifiers + if configuration_url is not UNDEFINED: + configuration_url = _validate_configuration_url(configuration_url) + for attr_name, value in ( ("area_id", area_id), ("configuration_url", configuration_url), diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index 0210d7ba75d..9ebee025bd5 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -1730,3 +1730,44 @@ async def test_device_info_configuration_url_validation( device_registry.async_update_device( update_device.id, configuration_url=configuration_url ) + + +@pytest.mark.parametrize("load_registries", [False]) +async def test_loading_invalid_configuration_url_from_storage( + hass: HomeAssistant, hass_storage: dict[str, Any] +) -> None: + """Test loading stored devices with an invalid URL.""" + hass_storage[dr.STORAGE_KEY] = { + "version": dr.STORAGE_VERSION_MAJOR, + "minor_version": dr.STORAGE_VERSION_MINOR, + "data": { + "devices": [ + { + "area_id": None, + "config_entries": ["1234"], + "configuration_url": "invalid", + "connections": [], + "disabled_by": None, + "entry_type": dr.DeviceEntryType.SERVICE, + "hw_version": None, + "id": "abcdefghijklm", + "identifiers": [["serial", "12:34:56:AB:CD:EF"]], + "manufacturer": None, + "model": None, + "name_by_user": None, + "name": None, + "sw_version": None, + "via_device_id": None, + } + ], + "deleted_devices": [], + }, + } + + await dr.async_load(hass) + registry = dr.async_get(hass) + assert len(registry.devices) == 1 + entry = registry.async_get_or_create( + config_entry_id="1234", identifiers={("serial", "12:34:56:AB:CD:EF")} + ) + assert entry.configuration_url == "invalid" From f7688c5e3bfd28a463c62650c8c1f1fd70b641f0 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 1 Aug 2023 22:29:16 +0200 Subject: [PATCH 64/71] Ensure we have an valid configuration URL in NetGear (#97590) --- homeassistant/components/netgear/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/netgear/__init__.py b/homeassistant/components/netgear/__init__.py index ef31a887691..522b60749d0 100644 --- a/homeassistant/components/netgear/__init__.py +++ b/homeassistant/components/netgear/__init__.py @@ -62,6 +62,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: entry.async_on_unload(entry.add_update_listener(update_listener)) + configuration_url = None + if host := entry.data[CONF_HOST]: + configuration_url = f"http://{host}/" + assert entry.unique_id device_registry = dr.async_get(hass) device_registry.async_get_or_create( @@ -72,7 +76,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: model=router.model, sw_version=router.firmware_version, hw_version=router.hardware_version, - configuration_url=f"http://{entry.data[CONF_HOST]}/", + configuration_url=configuration_url, ) async def async_update_devices() -> bool: From d115a372ae0fb8d1e9cd69b7d5ce9da271ceba72 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 1 Aug 2023 23:17:04 +0200 Subject: [PATCH 65/71] Bumped version to 2023.8.0b4 --- 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 936393cc8cb..0dfe6664dcc 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from typing import Final APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2023 MINOR_VERSION: Final = 8 -PATCH_VERSION: Final = "0b3" +PATCH_VERSION: Final = "0b4" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0) diff --git a/pyproject.toml b/pyproject.toml index a47d883d146..692ad56dcc7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2023.8.0b3" +version = "2023.8.0b4" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 14850a23f3fd1045c0adc0917bca908356aa4223 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 1 Aug 2023 20:19:22 -1000 Subject: [PATCH 66/71] Bump zeroconf to 0.72.0 (#97594) --- homeassistant/components/zeroconf/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zeroconf/manifest.json b/homeassistant/components/zeroconf/manifest.json index 92daffc6c8b..73ebe15d0c7 100644 --- a/homeassistant/components/zeroconf/manifest.json +++ b/homeassistant/components/zeroconf/manifest.json @@ -8,5 +8,5 @@ "iot_class": "local_push", "loggers": ["zeroconf"], "quality_scale": "internal", - "requirements": ["zeroconf==0.71.4"] + "requirements": ["zeroconf==0.72.0"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 3a887804470..076e534a6b0 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -52,7 +52,7 @@ voluptuous-serialize==2.6.0 voluptuous==0.13.1 webrtcvad==2.0.10 yarl==1.9.2 -zeroconf==0.71.4 +zeroconf==0.72.0 # Constrain pycryptodome to avoid vulnerability # see https://github.com/home-assistant/core/pull/16238 diff --git a/requirements_all.txt b/requirements_all.txt index 8d52c7a3927..a1f09f5ec36 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2749,7 +2749,7 @@ zamg==0.2.4 zengge==0.2 # homeassistant.components.zeroconf -zeroconf==0.71.4 +zeroconf==0.72.0 # homeassistant.components.zeversolar zeversolar==0.3.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2a7ec674fc3..395417cebb4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2022,7 +2022,7 @@ youtubeaio==1.1.5 zamg==0.2.4 # homeassistant.components.zeroconf -zeroconf==0.71.4 +zeroconf==0.72.0 # homeassistant.components.zeversolar zeversolar==0.3.1 From f0e640346fb04c8ab32b283dec5df3bef9065204 Mon Sep 17 00:00:00 2001 From: Jack Boswell Date: Wed, 2 Aug 2023 18:12:49 +1200 Subject: [PATCH 67/71] Fix Starlink Roaming name being blank (#97597) --- homeassistant/components/starlink/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/starlink/strings.json b/homeassistant/components/starlink/strings.json index aa89d87b6be..a9e50f5d39f 100644 --- a/homeassistant/components/starlink/strings.json +++ b/homeassistant/components/starlink/strings.json @@ -16,7 +16,7 @@ }, "entity": { "binary_sensor": { - "roaming_mode": { + "roaming": { "name": "Roaming mode" }, "currently_obstructed": { From 641b5ee7e4b55851c825623c49f26242770bb513 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Wed, 2 Aug 2023 09:09:13 +0200 Subject: [PATCH 68/71] Fix duotecno's name to be sync with the docs (#97602) --- homeassistant/components/duotecno/manifest.json | 2 +- homeassistant/generated/integrations.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/duotecno/manifest.json b/homeassistant/components/duotecno/manifest.json index 3089e3b515b..ae82574146e 100644 --- a/homeassistant/components/duotecno/manifest.json +++ b/homeassistant/components/duotecno/manifest.json @@ -1,6 +1,6 @@ { "domain": "duotecno", - "name": "duotecno", + "name": "Duotecno", "codeowners": ["@cereal2nd"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/duotecno", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index a3a8c334c11..350bcde8236 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -1241,7 +1241,7 @@ "iot_class": "local_polling" }, "duotecno": { - "name": "duotecno", + "name": "Duotecno", "integration_type": "hub", "config_flow": true, "iot_class": "local_push" From f81acc567bc612b0be952afd329394d5cd332659 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Wed, 2 Aug 2023 11:26:25 +0200 Subject: [PATCH 69/71] Add rounding back when unique_id is not set (#97603) --- .../components/history_stats/sensor.py | 5 +- tests/components/history_stats/test_sensor.py | 87 +++++++++++++++---- 2 files changed, 76 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/history_stats/sensor.py b/homeassistant/components/history_stats/sensor.py index 958f46a5e04..baa39468bc1 100644 --- a/homeassistant/components/history_stats/sensor.py +++ b/homeassistant/components/history_stats/sensor.py @@ -174,7 +174,10 @@ class HistoryStatsSensor(HistoryStatsSensorBase): return if self._type == CONF_TYPE_TIME: - self._attr_native_value = state.seconds_matched / 3600 + value = state.seconds_matched / 3600 + if self._attr_unique_id is None: + value = round(value, 2) + self._attr_native_value = value elif self._type == CONF_TYPE_RATIO: self._attr_native_value = pretty_ratio(state.seconds_matched, state.period) elif self._type == CONF_TYPE_COUNT: diff --git a/tests/components/history_stats/test_sensor.py b/tests/components/history_stats/test_sensor.py index ddd11c0d768..bb4b5b275d2 100644 --- a/tests/components/history_stats/test_sensor.py +++ b/tests/components/history_stats/test_sensor.py @@ -386,6 +386,7 @@ async def test_measure(recorder_mock: Recorder, hass: HomeAssistant) -> None: "start": "{{ as_timestamp(utcnow()) - 3600 }}", "end": "{{ utcnow() }}", "type": "time", + "unique_id": "6b1f54e3-4065-43ca-8492-d0d4506a573a", }, { "platform": "history_stats", @@ -413,7 +414,7 @@ async def test_measure(recorder_mock: Recorder, hass: HomeAssistant) -> None: await async_update_entity(hass, f"sensor.sensor{i}") await hass.async_block_till_done() - assert hass.states.get("sensor.sensor1").state == "0.833333333333333" + assert hass.states.get("sensor.sensor1").state == "0.83" assert hass.states.get("sensor.sensor2").state == "0.833333333333333" assert hass.states.get("sensor.sensor3").state == "2" assert hass.states.get("sensor.sensor4").state == "83.3" @@ -724,7 +725,17 @@ async def test_async_start_from_history_and_switch_to_watching_state_changes_sin "start": "{{ utcnow().replace(hour=0, minute=0, second=0) }}", "end": "{{ utcnow() }}", "type": "time", - } + }, + { + "platform": "history_stats", + "entity_id": "binary_sensor.state", + "name": "sensor2", + "state": "on", + "start": "{{ utcnow().replace(hour=0, minute=0, second=0) }}", + "end": "{{ utcnow() }}", + "type": "time", + "unique_id": "6b1f54e3-4065-43ca-8492-d0d4506a573a", + }, ] }, ) @@ -734,6 +745,7 @@ async def test_async_start_from_history_and_switch_to_watching_state_changes_sin await hass.async_block_till_done() assert hass.states.get("sensor.sensor1").state == "0.0" + assert hass.states.get("sensor.sensor2").state == "0.0" one_hour_in = start_time + timedelta(minutes=60) with freeze_time(one_hour_in): @@ -741,6 +753,7 @@ async def test_async_start_from_history_and_switch_to_watching_state_changes_sin await hass.async_block_till_done() assert hass.states.get("sensor.sensor1").state == "1.0" + assert hass.states.get("sensor.sensor2").state == "1.0" turn_off_time = start_time + timedelta(minutes=90) with freeze_time(turn_off_time): @@ -750,6 +763,7 @@ async def test_async_start_from_history_and_switch_to_watching_state_changes_sin await hass.async_block_till_done() assert hass.states.get("sensor.sensor1").state == "1.5" + assert hass.states.get("sensor.sensor2").state == "1.5" turn_back_on_time = start_time + timedelta(minutes=105) with freeze_time(turn_back_on_time): @@ -757,19 +771,22 @@ async def test_async_start_from_history_and_switch_to_watching_state_changes_sin await hass.async_block_till_done() assert hass.states.get("sensor.sensor1").state == "1.5" + assert hass.states.get("sensor.sensor2").state == "1.5" with freeze_time(turn_back_on_time): hass.states.async_set("binary_sensor.state", "on") await hass.async_block_till_done() assert hass.states.get("sensor.sensor1").state == "1.5" + assert hass.states.get("sensor.sensor2").state == "1.5" next_update_time = start_time + timedelta(minutes=107) with freeze_time(next_update_time): async_fire_time_changed(hass, next_update_time) await hass.async_block_till_done() - assert hass.states.get("sensor.sensor1").state == "1.53333333333333" + assert hass.states.get("sensor.sensor1").state == "1.53" + assert hass.states.get("sensor.sensor2").state == "1.53333333333333" end_time = start_time + timedelta(minutes=120) with freeze_time(end_time): @@ -777,6 +794,7 @@ async def test_async_start_from_history_and_switch_to_watching_state_changes_sin await hass.async_block_till_done() assert hass.states.get("sensor.sensor1").state == "1.75" + assert hass.states.get("sensor.sensor2").state == "1.75" async def test_async_start_from_history_and_switch_to_watching_state_changes_multiple( @@ -960,7 +978,17 @@ async def test_does_not_work_into_the_future( "start": "{{ utcnow().replace(hour=23, minute=0, second=0) }}", "duration": {"hours": 1}, "type": "time", - } + }, + { + "platform": "history_stats", + "entity_id": "binary_sensor.state", + "name": "sensor2", + "state": "on", + "start": "{{ utcnow().replace(hour=23, minute=0, second=0) }}", + "duration": {"hours": 1}, + "type": "time", + "unique_id": "6b1f54e3-4065-43ca-8492-d0d4506a573a", + }, ] }, ) @@ -969,6 +997,7 @@ async def test_does_not_work_into_the_future( await hass.async_block_till_done() assert hass.states.get("sensor.sensor1").state == STATE_UNKNOWN + assert hass.states.get("sensor.sensor2").state == STATE_UNKNOWN one_hour_in = start_time + timedelta(minutes=60) with freeze_time(one_hour_in): @@ -976,6 +1005,7 @@ async def test_does_not_work_into_the_future( await hass.async_block_till_done() assert hass.states.get("sensor.sensor1").state == STATE_UNKNOWN + assert hass.states.get("sensor.sensor2").state == STATE_UNKNOWN turn_off_time = start_time + timedelta(minutes=90) with freeze_time(turn_off_time): @@ -985,6 +1015,7 @@ async def test_does_not_work_into_the_future( await hass.async_block_till_done() assert hass.states.get("sensor.sensor1").state == STATE_UNKNOWN + assert hass.states.get("sensor.sensor2").state == STATE_UNKNOWN turn_back_on_time = start_time + timedelta(minutes=105) with freeze_time(turn_back_on_time): @@ -992,12 +1023,14 @@ async def test_does_not_work_into_the_future( await hass.async_block_till_done() assert hass.states.get("sensor.sensor1").state == STATE_UNKNOWN + assert hass.states.get("sensor.sensor2").state == STATE_UNKNOWN with freeze_time(turn_back_on_time): hass.states.async_set("binary_sensor.state", "on") await hass.async_block_till_done() assert hass.states.get("sensor.sensor1").state == STATE_UNKNOWN + assert hass.states.get("sensor.sensor2").state == STATE_UNKNOWN end_time = start_time + timedelta(minutes=120) with freeze_time(end_time): @@ -1005,13 +1038,15 @@ async def test_does_not_work_into_the_future( await hass.async_block_till_done() assert hass.states.get("sensor.sensor1").state == STATE_UNKNOWN + assert hass.states.get("sensor.sensor2").state == STATE_UNKNOWN in_the_window = start_time + timedelta(hours=23, minutes=5) with freeze_time(in_the_window): async_fire_time_changed(hass, in_the_window) await hass.async_block_till_done() - assert hass.states.get("sensor.sensor1").state == "0.0833333333333333" + assert hass.states.get("sensor.sensor1").state == "0.08" + assert hass.states.get("sensor.sensor2").state == "0.0833333333333333" past_the_window = start_time + timedelta(hours=25) with patch( @@ -1143,6 +1178,7 @@ async def test_measure_sliding_window( "start": "{{ as_timestamp(now()) - 3600 }}", "end": "{{ as_timestamp(now()) + 3600 }}", "type": "time", + "unique_id": "6b1f54e3-4065-43ca-8492-d0d4506a573a", }, { "platform": "history_stats", @@ -1175,7 +1211,7 @@ async def test_measure_sliding_window( await async_update_entity(hass, f"sensor.sensor{i}") await hass.async_block_till_done() - assert hass.states.get("sensor.sensor1").state == "0.833333333333333" + assert hass.states.get("sensor.sensor1").state == "0.83" assert hass.states.get("sensor.sensor2").state == "0.833333333333333" assert hass.states.get("sensor.sensor3").state == "2" assert hass.states.get("sensor.sensor4").state == "41.7" @@ -1188,7 +1224,7 @@ async def test_measure_sliding_window( async_fire_time_changed(hass, past_next_update) await hass.async_block_till_done() - assert hass.states.get("sensor.sensor1").state == "0.833333333333333" + assert hass.states.get("sensor.sensor1").state == "0.83" assert hass.states.get("sensor.sensor2").state == "0.833333333333333" assert hass.states.get("sensor.sensor3").state == "2" assert hass.states.get("sensor.sensor4").state == "41.7" @@ -1242,6 +1278,7 @@ async def test_measure_from_end_going_backwards( "duration": {"hours": 1}, "end": "{{ utcnow() }}", "type": "time", + "unique_id": "6b1f54e3-4065-43ca-8492-d0d4506a573a", }, { "platform": "history_stats", @@ -1269,7 +1306,7 @@ async def test_measure_from_end_going_backwards( await async_update_entity(hass, f"sensor.sensor{i}") await hass.async_block_till_done() - assert hass.states.get("sensor.sensor1").state == "0.833333333333333" + assert hass.states.get("sensor.sensor1").state == "0.83" assert hass.states.get("sensor.sensor2").state == "0.833333333333333" assert hass.states.get("sensor.sensor3").state == "2" assert hass.states.get("sensor.sensor4").state == "83.3" @@ -1282,7 +1319,7 @@ async def test_measure_from_end_going_backwards( async_fire_time_changed(hass, past_next_update) await hass.async_block_till_done() - assert hass.states.get("sensor.sensor1").state == "0.833333333333333" + assert hass.states.get("sensor.sensor1").state == "0.83" assert hass.states.get("sensor.sensor2").state == "0.833333333333333" assert hass.states.get("sensor.sensor3").state == "2" assert hass.states.get("sensor.sensor4").state == "83.3" @@ -1335,6 +1372,7 @@ async def test_measure_cet(recorder_mock: Recorder, hass: HomeAssistant) -> None "start": "{{ as_timestamp(utcnow()) - 3600 }}", "end": "{{ utcnow() }}", "type": "time", + "unique_id": "6b1f54e3-4065-43ca-8492-d0d4506a573a", }, { "platform": "history_stats", @@ -1362,7 +1400,7 @@ async def test_measure_cet(recorder_mock: Recorder, hass: HomeAssistant) -> None await async_update_entity(hass, f"sensor.sensor{i}") await hass.async_block_till_done() - assert hass.states.get("sensor.sensor1").state == "0.833333333333333" + assert hass.states.get("sensor.sensor1").state == "0.83" assert hass.states.get("sensor.sensor2").state == "0.833333333333333" assert hass.states.get("sensor.sensor3").state == "2" assert hass.states.get("sensor.sensor4").state == "83.3" @@ -1425,20 +1463,33 @@ async def test_end_time_with_microseconds_zeroed( "end": "{{ now().replace(microsecond=0) }}", "type": "time", }, + { + "platform": "history_stats", + "entity_id": "binary_sensor.heatpump_compressor_state", + "name": "heatpump_compressor_today2", + "state": "on", + "start": "{{ now().replace(hour=0, minute=0, second=0, microsecond=0) }}", + "end": "{{ now().replace(microsecond=0) }}", + "type": "time", + "unique_id": "6b1f54e3-4065-43ca-8492-d0d4506a573a", + }, ] }, ) await hass.async_block_till_done() await async_update_entity(hass, "sensor.heatpump_compressor_today") await hass.async_block_till_done() + assert hass.states.get("sensor.heatpump_compressor_today").state == "1.83" assert ( - hass.states.get("sensor.heatpump_compressor_today").state + hass.states.get("sensor.heatpump_compressor_today2").state == "1.83333333333333" ) + async_fire_time_changed(hass, time_200) await hass.async_block_till_done() + assert hass.states.get("sensor.heatpump_compressor_today").state == "1.83" assert ( - hass.states.get("sensor.heatpump_compressor_today").state + hass.states.get("sensor.heatpump_compressor_today2").state == "1.83333333333333" ) hass.states.async_set("binary_sensor.heatpump_compressor_state", "off") @@ -1448,8 +1499,9 @@ async def test_end_time_with_microseconds_zeroed( with freeze_time(time_400): async_fire_time_changed(hass, time_400) await hass.async_block_till_done() + assert hass.states.get("sensor.heatpump_compressor_today").state == "1.83" assert ( - hass.states.get("sensor.heatpump_compressor_today").state + hass.states.get("sensor.heatpump_compressor_today2").state == "1.83333333333333" ) hass.states.async_set("binary_sensor.heatpump_compressor_state", "on") @@ -1458,8 +1510,9 @@ async def test_end_time_with_microseconds_zeroed( with freeze_time(time_600): async_fire_time_changed(hass, time_600) await hass.async_block_till_done() + assert hass.states.get("sensor.heatpump_compressor_today").state == "3.83" assert ( - hass.states.get("sensor.heatpump_compressor_today").state + hass.states.get("sensor.heatpump_compressor_today2").state == "3.83333333333333" ) @@ -1473,6 +1526,7 @@ async def test_end_time_with_microseconds_zeroed( async_fire_time_changed(hass, rolled_to_next_day) await hass.async_block_till_done() assert hass.states.get("sensor.heatpump_compressor_today").state == "0.0" + assert hass.states.get("sensor.heatpump_compressor_today2").state == "0.0" rolled_to_next_day_plus_12 = start_of_today + timedelta( days=1, hours=12, microseconds=0 @@ -1481,6 +1535,7 @@ async def test_end_time_with_microseconds_zeroed( async_fire_time_changed(hass, rolled_to_next_day_plus_12) await hass.async_block_till_done() assert hass.states.get("sensor.heatpump_compressor_today").state == "12.0" + assert hass.states.get("sensor.heatpump_compressor_today2").state == "12.0" rolled_to_next_day_plus_14 = start_of_today + timedelta( days=1, hours=14, microseconds=0 @@ -1489,6 +1544,7 @@ async def test_end_time_with_microseconds_zeroed( async_fire_time_changed(hass, rolled_to_next_day_plus_14) await hass.async_block_till_done() assert hass.states.get("sensor.heatpump_compressor_today").state == "14.0" + assert hass.states.get("sensor.heatpump_compressor_today2").state == "14.0" rolled_to_next_day_plus_16_860000 = start_of_today + timedelta( days=1, hours=16, microseconds=860000 @@ -1503,8 +1559,9 @@ async def test_end_time_with_microseconds_zeroed( with freeze_time(rolled_to_next_day_plus_18): async_fire_time_changed(hass, rolled_to_next_day_plus_18) await hass.async_block_till_done() + assert hass.states.get("sensor.heatpump_compressor_today").state == "16.0" assert ( - hass.states.get("sensor.heatpump_compressor_today").state + hass.states.get("sensor.heatpump_compressor_today2").state == "16.0002388888929" ) From 598dece947012a9b28a6e8d6527f02b2e5b6284c Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 2 Aug 2023 13:05:31 +0200 Subject: [PATCH 70/71] Bumped version to 2023.8.0 --- 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 0dfe6664dcc..db7ef9e305a 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from typing import Final APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2023 MINOR_VERSION: Final = 8 -PATCH_VERSION: Final = "0b4" +PATCH_VERSION: Final = "0" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0) diff --git a/pyproject.toml b/pyproject.toml index 692ad56dcc7..26a9525a176 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2023.8.0b4" +version = "2023.8.0" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 445aaa026707e36f92fb7f5ea07e1b9c32070196 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 2 Aug 2023 14:43:42 +0200 Subject: [PATCH 71/71] Update frontend to 20230802.0 (#97614) --- 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 2210a44039e..84d1d4f5e27 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==20230801.0"] + "requirements": ["home-assistant-frontend==20230802.0"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 076e534a6b0..7401c747890 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -22,7 +22,7 @@ ha-av==10.1.1 hass-nabucasa==0.69.0 hassil==1.2.5 home-assistant-bluetooth==1.10.2 -home-assistant-frontend==20230801.0 +home-assistant-frontend==20230802.0 home-assistant-intents==2023.7.25 httpx==0.24.1 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index a1f09f5ec36..28140477411 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -988,7 +988,7 @@ hole==0.8.0 holidays==0.28 # homeassistant.components.frontend -home-assistant-frontend==20230801.0 +home-assistant-frontend==20230802.0 # homeassistant.components.conversation home-assistant-intents==2023.7.25 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 395417cebb4..03dc3bbf994 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -774,7 +774,7 @@ hole==0.8.0 holidays==0.28 # homeassistant.components.frontend -home-assistant-frontend==20230801.0 +home-assistant-frontend==20230802.0 # homeassistant.components.conversation home-assistant-intents==2023.7.25