From 8aa672882af6e5db495c76a146b321cd9dc13935 Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Mon, 8 Sep 2025 17:59:18 +0200 Subject: [PATCH 01/15] Replace "STT" with "Speech-to-Text" in `google_cloud` UI (#151918) --- homeassistant/components/google_cloud/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/google_cloud/strings.json b/homeassistant/components/google_cloud/strings.json index 3bf9d8c84898c..4b3ffa1c01232 100644 --- a/homeassistant/components/google_cloud/strings.json +++ b/homeassistant/components/google_cloud/strings.json @@ -25,7 +25,7 @@ "gain": "Default volume gain (in dB) of the voice", "profiles": "Default audio profiles", "text_type": "Default text type", - "stt_model": "STT model" + "stt_model": "Speech-to-Text model" } } } From acc75e4419207893e88aff6d36568a6c393fdcea Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 8 Sep 2025 18:01:41 +0200 Subject: [PATCH 02/15] Remove image filename parameter from Google Generative AI (#151914) --- .../__init__.py | 18 +----------------- .../services.yaml | 4 ---- .../strings.json | 11 ----------- 3 files changed, 1 insertion(+), 32 deletions(-) diff --git a/homeassistant/components/google_generative_ai_conversation/__init__.py b/homeassistant/components/google_generative_ai_conversation/__init__.py index 8d7fb1b1cc4cb..86966937057f9 100644 --- a/homeassistant/components/google_generative_ai_conversation/__init__.py +++ b/homeassistant/components/google_generative_ai_conversation/__init__.py @@ -30,7 +30,6 @@ device_registry as dr, entity_registry as er, ) -from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.typing import ConfigType from .const import ( @@ -72,18 +71,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def generate_content(call: ServiceCall) -> ServiceResponse: """Generate content from text and optionally images.""" - if call.data[CONF_IMAGE_FILENAME]: - # Deprecated in 2025.3, to remove in 2025.9 - async_create_issue( - hass, - DOMAIN, - "deprecated_image_filename_parameter", - breaks_in_ha_version="2025.9.0", - is_fixable=False, - severity=IssueSeverity.WARNING, - translation_key="deprecated_image_filename_parameter", - ) - prompt_parts = [call.data[CONF_PROMPT]] config_entry: GoogleGenerativeAIConfigEntry = ( @@ -92,7 +79,7 @@ async def generate_content(call: ServiceCall) -> ServiceResponse: client = config_entry.runtime_data - files = call.data[CONF_IMAGE_FILENAME] + call.data[CONF_FILENAMES] + files = call.data[CONF_FILENAMES] if files: for filename in files: @@ -140,9 +127,6 @@ async def generate_content(call: ServiceCall) -> ServiceResponse: schema=vol.Schema( { vol.Required(CONF_PROMPT): cv.string, - vol.Optional(CONF_IMAGE_FILENAME, default=[]): vol.All( - cv.ensure_list, [cv.string] - ), vol.Optional(CONF_FILENAMES, default=[]): vol.All( cv.ensure_list, [cv.string] ), diff --git a/homeassistant/components/google_generative_ai_conversation/services.yaml b/homeassistant/components/google_generative_ai_conversation/services.yaml index 82190d6454001..30077dec6507c 100644 --- a/homeassistant/components/google_generative_ai_conversation/services.yaml +++ b/homeassistant/components/google_generative_ai_conversation/services.yaml @@ -5,10 +5,6 @@ generate_content: selector: text: multiline: true - image_filename: - required: false - selector: - object: filenames: required: false selector: diff --git a/homeassistant/components/google_generative_ai_conversation/strings.json b/homeassistant/components/google_generative_ai_conversation/strings.json index 545436da59037..43008332e6879 100644 --- a/homeassistant/components/google_generative_ai_conversation/strings.json +++ b/homeassistant/components/google_generative_ai_conversation/strings.json @@ -160,11 +160,6 @@ "description": "The prompt", "example": "Describe what you see in these images" }, - "image_filename": { - "name": "Image filename", - "description": "Deprecated. Use filenames instead.", - "example": "/config/www/image.jpg" - }, "filenames": { "name": "Attachment filenames", "description": "Attachments to add to the prompt (images, PDFs, etc)", @@ -172,11 +167,5 @@ } } } - }, - "issues": { - "deprecated_image_filename_parameter": { - "title": "Deprecated 'image_filename' parameter", - "description": "The 'image_filename' parameter in Google Generative AI actions is deprecated. Please edit scripts and automations to use 'filenames' instead." - } } } From 0ab232b904e4d5e6ca8ce8ed7ef90e5a3b56579c Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Mon, 8 Sep 2025 18:43:15 +0200 Subject: [PATCH 03/15] Fix typo in MQTT strings (#151907) --- homeassistant/components/mqtt/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/mqtt/strings.json b/homeassistant/components/mqtt/strings.json index dce546b3e6d63..860336735f43f 100644 --- a/homeassistant/components/mqtt/strings.json +++ b/homeassistant/components/mqtt/strings.json @@ -2,7 +2,7 @@ "issues": { "deprecated_object_id": { "title": "Deprecated option object_id used", - "description": "Entity {entity_id} uses the `object_id` option which deprecated. To fix the issue, replace the `object_id: {object_id}` option with `default_entity_id: {domain}.{object_id}` in your \"configuration.yaml\", and restart Home Assistant." + "description": "Entity {entity_id} uses the `object_id` option which is deprecated. To fix the issue, replace the `object_id: {object_id}` option with `default_entity_id: {domain}.{object_id}` in your \"configuration.yaml\", and restart Home Assistant." }, "deprecated_vacuum_battery_feature": { "title": "Deprecated battery feature used", From df3d4b5db14aba1d8c03e4ed304e3f88383c30ca Mon Sep 17 00:00:00 2001 From: Artur Pragacz <49985303+arturpragacz@users.noreply.github.com> Date: Mon, 8 Sep 2025 19:42:23 +0200 Subject: [PATCH 04/15] Clean up unused intent category (#151917) --- .../components/assist_satellite/intent.py | 1 - homeassistant/components/climate/intent.py | 1 - .../components/conversation/default_agent.py | 1 - .../components/media_player/intent.py | 2 -- .../components/shopping_list/intent.py | 1 - homeassistant/components/todo/intent.py | 2 -- homeassistant/helpers/intent.py | 20 +------------------ 7 files changed, 1 insertion(+), 27 deletions(-) diff --git a/homeassistant/components/assist_satellite/intent.py b/homeassistant/components/assist_satellite/intent.py index 7612753e8c497..24958b36153d7 100644 --- a/homeassistant/components/assist_satellite/intent.py +++ b/homeassistant/components/assist_satellite/intent.py @@ -75,7 +75,6 @@ async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse ) response = intent_obj.create_response() - response.response_type = intent.IntentResponseType.ACTION_DONE response.async_set_results( success_results=[ intent.IntentResponseTarget( diff --git a/homeassistant/components/climate/intent.py b/homeassistant/components/climate/intent.py index 7691a2db0f144..6f820ce0837fe 100644 --- a/homeassistant/components/climate/intent.py +++ b/homeassistant/components/climate/intent.py @@ -89,7 +89,6 @@ async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse ) response = intent_obj.create_response() - response.response_type = intent.IntentResponseType.ACTION_DONE response.async_set_results( success_results=[ intent.IntentResponseTarget( diff --git a/homeassistant/components/conversation/default_agent.py b/homeassistant/components/conversation/default_agent.py index 938889955e960..4e07fd0135fc0 100644 --- a/homeassistant/components/conversation/default_agent.py +++ b/homeassistant/components/conversation/default_agent.py @@ -371,7 +371,6 @@ async def _async_handle_message( response = intent.IntentResponse( language=user_input.language or self.hass.config.language ) - response.response_type = intent.IntentResponseType.ACTION_DONE response.async_set_speech(response_text) if response is None: diff --git a/homeassistant/components/media_player/intent.py b/homeassistant/components/media_player/intent.py index 9b714fdf52dbc..c45dc83e872ec 100644 --- a/homeassistant/components/media_player/intent.py +++ b/homeassistant/components/media_player/intent.py @@ -355,7 +355,6 @@ async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse # Success response = intent_obj.create_response() response.async_set_speech_slots({"media": first_result.as_dict()}) - response.response_type = intent.IntentResponseType.ACTION_DONE return response @@ -471,6 +470,5 @@ async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse ) from err response = intent_obj.create_response() - response.response_type = intent.IntentResponseType.ACTION_DONE response.async_set_states(match_result.states) return response diff --git a/homeassistant/components/shopping_list/intent.py b/homeassistant/components/shopping_list/intent.py index 29e366fc5ddb1..06bb692621ad1 100644 --- a/homeassistant/components/shopping_list/intent.py +++ b/homeassistant/components/shopping_list/intent.py @@ -60,7 +60,6 @@ async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse response = intent_obj.create_response() response.async_set_speech_slots({"completed_items": complete_items}) - response.response_type = intent.IntentResponseType.ACTION_DONE return response diff --git a/homeassistant/components/todo/intent.py b/homeassistant/components/todo/intent.py index d679a57bf9648..a1379b003f680 100644 --- a/homeassistant/components/todo/intent.py +++ b/homeassistant/components/todo/intent.py @@ -65,7 +65,6 @@ async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse ) response: intent.IntentResponse = intent_obj.create_response() - response.response_type = intent.IntentResponseType.ACTION_DONE response.async_set_results( [ intent.IntentResponseTarget( @@ -141,7 +140,6 @@ async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse ) response: intent.IntentResponse = intent_obj.create_response() - response.response_type = intent.IntentResponseType.ACTION_DONE response.async_set_results( [ intent.IntentResponseTarget( diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 75572194bb8ad..de6f98527c59e 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -1264,22 +1264,11 @@ def get_domain_and_service( return (self.domain, self.service) -class IntentCategory(Enum): - """Category of an intent.""" - - ACTION = "action" - """Trigger an action like turning an entity on or off""" - - QUERY = "query" - """Get information about the state of an entity""" - - class Intent: """Hold the intent.""" __slots__ = [ "assistant", - "category", "context", "conversation_agent_id", "device_id", @@ -1300,7 +1289,6 @@ def __init__( text_input: str | None, context: Context, language: str, - category: IntentCategory | None = None, assistant: str | None = None, device_id: str | None = None, conversation_agent_id: str | None = None, @@ -1313,7 +1301,6 @@ def __init__( self.text_input = text_input self.context = context self.language = language - self.category = category self.assistant = assistant self.device_id = device_id self.conversation_agent_id = conversation_agent_id @@ -1398,12 +1385,7 @@ def __init__( self.matched_states: list[State] = [] self.unmatched_states: list[State] = [] self.speech_slots: dict[str, Any] = {} - - if (self.intent is not None) and (self.intent.category == IntentCategory.QUERY): - # speech will be the answer to the query - self.response_type = IntentResponseType.QUERY_ANSWER - else: - self.response_type = IntentResponseType.ACTION_DONE + self.response_type = IntentResponseType.ACTION_DONE @callback def async_set_speech( From b5704f3e8b162b2623e2a22f4900c3a5d7d9f7a1 Mon Sep 17 00:00:00 2001 From: Markus Adrario Date: Mon, 8 Sep 2025 19:53:13 +0200 Subject: [PATCH 05/15] Bump pyHomee to 1.3.8 (#151874) --- homeassistant/components/homee/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homee/manifest.json b/homeassistant/components/homee/manifest.json index 35e89ec645ada..4304239cf1c26 100644 --- a/homeassistant/components/homee/manifest.json +++ b/homeassistant/components/homee/manifest.json @@ -8,7 +8,7 @@ "iot_class": "local_push", "loggers": ["homee"], "quality_scale": "silver", - "requirements": ["pyHomee==1.2.10"], + "requirements": ["pyHomee==1.3.8"], "zeroconf": [ { "type": "_ssh._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index 4f8e9fcc7b7da..3ace7a1e5c885 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1813,7 +1813,7 @@ pyEmby==1.10 pyHik==0.3.2 # homeassistant.components.homee -pyHomee==1.2.10 +pyHomee==1.3.8 # homeassistant.components.rfxtrx pyRFXtrx==0.31.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0ca7f98889957..335e18624dc60 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1527,7 +1527,7 @@ pyDuotecno==2024.10.1 pyElectra==1.2.4 # homeassistant.components.homee -pyHomee==1.2.10 +pyHomee==1.3.8 # homeassistant.components.rfxtrx pyRFXtrx==0.31.1 From dbdbf1cf16048bb8da8ec090b9dc15775cfb8791 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Sep 2025 13:37:50 -0500 Subject: [PATCH 06/15] Bump habluetooth to 5.5.1 (#151921) --- 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 b4d188550d36b..f2009cb07dc71 100644 --- a/homeassistant/components/bluetooth/manifest.json +++ b/homeassistant/components/bluetooth/manifest.json @@ -21,6 +21,6 @@ "bluetooth-auto-recovery==1.5.2", "bluetooth-data-tools==1.28.2", "dbus-fast==2.44.3", - "habluetooth==5.3.1" + "habluetooth==5.5.1" ] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index bd755009e1e47..893e63fdb03f5 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -34,7 +34,7 @@ dbus-fast==2.44.3 fnv-hash-fast==1.5.0 go2rtc-client==0.2.1 ha-ffmpeg==3.2.2 -habluetooth==5.3.1 +habluetooth==5.5.1 hass-nabucasa==1.1.0 hassil==3.2.0 home-assistant-bluetooth==1.13.1 diff --git a/requirements_all.txt b/requirements_all.txt index 3ace7a1e5c885..ea82928799464 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1137,7 +1137,7 @@ ha-silabs-firmware-client==0.2.0 habiticalib==0.4.5 # homeassistant.components.bluetooth -habluetooth==5.3.1 +habluetooth==5.5.1 # homeassistant.components.cloud hass-nabucasa==1.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 335e18624dc60..9d5baad1d72d4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -998,7 +998,7 @@ ha-silabs-firmware-client==0.2.0 habiticalib==0.4.5 # homeassistant.components.bluetooth -habluetooth==5.3.1 +habluetooth==5.5.1 # homeassistant.components.cloud hass-nabucasa==1.1.0 From e7cb0173b0c3639baaae96673096e96be9b4b2d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Mon, 8 Sep 2025 19:41:38 +0100 Subject: [PATCH 07/15] Increase timeout of install os dependencies step (#151931) --- .github/workflows/ci.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1737143afb7b3..2c510af307af6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -517,7 +517,7 @@ jobs: env.HA_SHORT_VERSION }}- - name: Install additional OS dependencies if: steps.cache-venv.outputs.cache-hit != 'true' - timeout-minutes: 5 + timeout-minutes: 10 run: | sudo rm /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get update @@ -579,7 +579,7 @@ jobs: - base steps: - name: Install additional OS dependencies - timeout-minutes: 5 + timeout-minutes: 10 run: | sudo rm /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get update @@ -879,7 +879,7 @@ jobs: name: Split tests for full run steps: - name: Install additional OS dependencies - timeout-minutes: 5 + timeout-minutes: 10 run: | sudo rm /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get update @@ -940,7 +940,7 @@ jobs: Run tests Python ${{ matrix.python-version }} (${{ matrix.group }}) steps: - name: Install additional OS dependencies - timeout-minutes: 5 + timeout-minutes: 10 run: | sudo rm /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get update @@ -1074,7 +1074,7 @@ jobs: Run ${{ matrix.mariadb-group }} tests Python ${{ matrix.python-version }} steps: - name: Install additional OS dependencies - timeout-minutes: 5 + timeout-minutes: 10 run: | sudo rm /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get update @@ -1215,7 +1215,7 @@ jobs: Run ${{ matrix.postgresql-group }} tests Python ${{ matrix.python-version }} steps: - name: Install additional OS dependencies - timeout-minutes: 5 + timeout-minutes: 10 run: | sudo rm /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get update @@ -1377,7 +1377,7 @@ jobs: Run tests Python ${{ matrix.python-version }} (${{ matrix.group }}) steps: - name: Install additional OS dependencies - timeout-minutes: 5 + timeout-minutes: 10 run: | sudo rm /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get update From 7ee7a3c0b53bf4136f4012cab637f86b02305b05 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Sep 2025 14:32:31 -0500 Subject: [PATCH 08/15] Bump bleak-esphome to 3.3.0 (#151922) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Abílio Costa --- homeassistant/components/eq3btsmart/manifest.json | 2 +- homeassistant/components/esphome/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/eq3btsmart/manifest.json b/homeassistant/components/eq3btsmart/manifest.json index 802ddae36e93c..7253cd79910f3 100644 --- a/homeassistant/components/eq3btsmart/manifest.json +++ b/homeassistant/components/eq3btsmart/manifest.json @@ -22,5 +22,5 @@ "integration_type": "device", "iot_class": "local_polling", "loggers": ["eq3btsmart"], - "requirements": ["eq3btsmart==2.1.0", "bleak-esphome==3.2.0"] + "requirements": ["eq3btsmart==2.1.0", "bleak-esphome==3.3.0"] } diff --git a/homeassistant/components/esphome/manifest.json b/homeassistant/components/esphome/manifest.json index 05da3dacbc46a..95e9aec11c420 100644 --- a/homeassistant/components/esphome/manifest.json +++ b/homeassistant/components/esphome/manifest.json @@ -19,7 +19,7 @@ "requirements": [ "aioesphomeapi==40.0.1", "esphome-dashboard-api==1.3.0", - "bleak-esphome==3.2.0" + "bleak-esphome==3.3.0" ], "zeroconf": ["_esphomelib._tcp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index ea82928799464..3e635c9191cef 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -628,7 +628,7 @@ bizkaibus==0.1.1 # homeassistant.components.eq3btsmart # homeassistant.components.esphome -bleak-esphome==3.2.0 +bleak-esphome==3.3.0 # homeassistant.components.bluetooth bleak-retry-connector==4.4.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9d5baad1d72d4..ede0356e9ff22 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -562,7 +562,7 @@ bimmer-connected[china]==0.17.3 # homeassistant.components.eq3btsmart # homeassistant.components.esphome -bleak-esphome==3.2.0 +bleak-esphome==3.3.0 # homeassistant.components.bluetooth bleak-retry-connector==4.4.3 From 82c3fcccc95787049bf2d34b6b79d93166500cf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Mon, 8 Sep 2025 20:37:18 +0100 Subject: [PATCH 09/15] Update whirlpool-sixth-sense to 0.21.3 (#151929) Co-authored-by: J. Nick Koston --- homeassistant/components/whirlpool/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/whirlpool/manifest.json b/homeassistant/components/whirlpool/manifest.json index 2712e6b2f6412..914201ab76fb3 100644 --- a/homeassistant/components/whirlpool/manifest.json +++ b/homeassistant/components/whirlpool/manifest.json @@ -8,5 +8,5 @@ "iot_class": "cloud_push", "loggers": ["whirlpool"], "quality_scale": "bronze", - "requirements": ["whirlpool-sixth-sense==0.21.1"] + "requirements": ["whirlpool-sixth-sense==0.21.3"] } diff --git a/requirements_all.txt b/requirements_all.txt index 3e635c9191cef..2f19db17d55a5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -3117,7 +3117,7 @@ webmin-xmlrpc==0.0.2 weheat==2025.6.10 # homeassistant.components.whirlpool -whirlpool-sixth-sense==0.21.1 +whirlpool-sixth-sense==0.21.3 # homeassistant.components.whois whois==0.9.27 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ede0356e9ff22..96f8b713957dc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2573,7 +2573,7 @@ webmin-xmlrpc==0.0.2 weheat==2025.6.10 # homeassistant.components.whirlpool -whirlpool-sixth-sense==0.21.1 +whirlpool-sixth-sense==0.21.3 # homeassistant.components.whois whois==0.9.27 From 4025e23c678e989d80d1e6c9f78e966457a601d8 Mon Sep 17 00:00:00 2001 From: Manu <4445816+tr4nt0r@users.noreply.github.com> Date: Mon, 8 Sep 2025 21:37:47 +0200 Subject: [PATCH 10/15] Remove unused translation string in Bring! integration (#151927) --- homeassistant/components/bring/strings.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/homeassistant/components/bring/strings.json b/homeassistant/components/bring/strings.json index 48677d5252391..6ce16ca52caf0 100644 --- a/homeassistant/components/bring/strings.json +++ b/homeassistant/components/bring/strings.json @@ -164,10 +164,6 @@ "name": "[%key:component::notify::services::notify::name%]", "description": "Sends a mobile push notification to members of a shared Bring! list.", "fields": { - "entity_id": { - "name": "List", - "description": "Bring! list whose members (except sender) will be notified." - }, "message": { "name": "Notification type", "description": "Type of push notification to send to list members." From 03a7052151216cf311ad6a4e2d092f6f723b0df8 Mon Sep 17 00:00:00 2001 From: Nathan Spencer Date: Mon, 8 Sep 2025 15:40:18 -0400 Subject: [PATCH 11/15] Add last feeding sensor for Feeder-Robots (#151871) --- homeassistant/components/litterrobot/sensor.py | 12 +++++++++++- homeassistant/components/litterrobot/strings.json | 3 +++ tests/components/litterrobot/test_sensor.py | 4 ++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/litterrobot/sensor.py b/homeassistant/components/litterrobot/sensor.py index aa7c3a451be15..33f803a52b51b 100644 --- a/homeassistant/components/litterrobot/sensor.py +++ b/homeassistant/components/litterrobot/sensor.py @@ -170,7 +170,17 @@ class RobotSensorEntityDescription(SensorEntityDescription, Generic[_WhiskerEnti icon_fn=lambda state: icon_for_gauge_level(state, 10), state_class=SensorStateClass.MEASUREMENT, value_fn=lambda robot: robot.food_level, - ) + ), + RobotSensorEntityDescription[FeederRobot]( + key="last_feeding", + translation_key="last_feeding", + device_class=SensorDeviceClass.TIMESTAMP, + value_fn=( + lambda robot: ( + robot.last_feeding["timestamp"] if robot.last_feeding else None + ) + ), + ), ], } diff --git a/homeassistant/components/litterrobot/strings.json b/homeassistant/components/litterrobot/strings.json index 35aff0f91051c..b5702ef855c8c 100644 --- a/homeassistant/components/litterrobot/strings.json +++ b/homeassistant/components/litterrobot/strings.json @@ -73,6 +73,9 @@ "empty": "[%key:common::state::empty%]" } }, + "last_feeding": { + "name": "Last feeding" + }, "last_seen": { "name": "Last seen" }, diff --git a/tests/components/litterrobot/test_sensor.py b/tests/components/litterrobot/test_sensor.py index d1101a4231d9a..09c5c3a3dad97 100644 --- a/tests/components/litterrobot/test_sensor.py +++ b/tests/components/litterrobot/test_sensor.py @@ -113,6 +113,10 @@ async def test_feeder_robot_sensor( assert sensor.state == "10" assert sensor.attributes["unit_of_measurement"] == PERCENTAGE + sensor = hass.states.get("sensor.test_last_feeding") + assert sensor.state == "2022-09-08T18:00:00+00:00" + assert sensor.attributes["device_class"] == SensorDeviceClass.TIMESTAMP + async def test_pet_weight_sensor( hass: HomeAssistant, mock_account_with_pet: MagicMock From d990c2bee2f3e66f699bc33e307b91302f9d956b Mon Sep 17 00:00:00 2001 From: michnovka Date: Mon, 8 Sep 2025 21:51:17 +0200 Subject: [PATCH 12/15] Fix timestamps exposed to LLM to be in local timezone (#139825) Co-authored-by: Michael Hansen --- homeassistant/helpers/llm.py | 5 +++ tests/helpers/test_llm.py | 67 ++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/homeassistant/helpers/llm.py b/homeassistant/helpers/llm.py index 5427c220c02f5..9a019551c1ec7 100644 --- a/homeassistant/helpers/llm.py +++ b/homeassistant/helpers/llm.py @@ -687,6 +687,11 @@ def _get_exposed_entities( if include_state: info["state"] = state.state + # Convert timestamp device_class states from UTC to local time + if state.attributes.get("device_class") == "timestamp" and state.state: + if (parsed_utc := dt_util.parse_datetime(state.state)) is not None: + info["state"] = dt_util.as_local(parsed_utc).isoformat() + if description: info["description"] = description diff --git a/tests/helpers/test_llm.py b/tests/helpers/test_llm.py index 6eef62a2c5452..accc681ca9d14 100644 --- a/tests/helpers/test_llm.py +++ b/tests/helpers/test_llm.py @@ -1530,3 +1530,70 @@ async def async_call( llm.ToolInput(tool_name="api-2__Tool_2", tool_args={"arg2": "value2"}) ) assert result == {"result": {"Tool_2": {"arg2": "value2"}}} + + +async def test_get_exposed_entities_timestamp_conversion(hass: HomeAssistant) -> None: + """Test that _get_exposed_entities converts timestamp states to local time.""" + assert await async_setup_component(hass, "homeassistant", {}) + + # Set the timezone to something other than UTC to ensure conversion is tested + await hass.config.async_set_time_zone("America/New_York") + + # Set up a timestamp sensor with UTC time + utc_timestamp = "2024-01-15T10:30:00+00:00" + hass.states.async_set( + "sensor.test_timestamp", + utc_timestamp, + {"device_class": "timestamp", "friendly_name": "Test Timestamp"}, + ) + + # Also test with a non-timestamp sensor to ensure it's not affected + hass.states.async_set( + "sensor.regular_sensor", + "2024-01-15T10:30:00+00:00", + {"friendly_name": "Regular Sensor"}, # No device_class + ) + + # And test with invalid/empty timestamp + hass.states.async_set( + "sensor.invalid_timestamp", + "not-a-timestamp", + {"device_class": "timestamp", "friendly_name": "Invalid Timestamp"}, + ) + + hass.states.async_set( + "sensor.empty_timestamp", + "", + {"device_class": "timestamp", "friendly_name": "Empty Timestamp"}, + ) + + # Expose the entities + async_expose_entity(hass, "conversation", "sensor.test_timestamp", True) + async_expose_entity(hass, "conversation", "sensor.regular_sensor", True) + async_expose_entity(hass, "conversation", "sensor.invalid_timestamp", True) + async_expose_entity(hass, "conversation", "sensor.empty_timestamp", True) + + # Call _get_exposed_entities + exposed = llm._get_exposed_entities(hass, "conversation", include_state=True) + + # Check the converted timestamp + sensor_info = exposed["entities"]["sensor.test_timestamp"] + + assert sensor_info["state"] == "2024-01-15T05:30:00-05:00" + # Regular sensor without device_class should keep original value + regular_info = exposed["entities"]["sensor.regular_sensor"] + assert regular_info["state"] == "2024-01-15T10:30:00+00:00" # Unchanged + + # Invalid timestamp should remain as-is + invalid_info = exposed["entities"]["sensor.invalid_timestamp"] + assert invalid_info["state"] == "not-a-timestamp" + + # Empty timestamp should remain empty + empty_info = exposed["entities"]["sensor.empty_timestamp"] + assert empty_info["state"] == "" + + # Test with include_state=False to ensure no conversion happens + exposed_no_state = llm._get_exposed_entities( + hass, "conversation", include_state=False + ) + assert "state" not in exposed_no_state["entities"]["sensor.test_timestamp"] From 04444678587f55f2c72654e94e2c5b0d25244eb3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 8 Sep 2025 16:11:06 -0400 Subject: [PATCH 13/15] Gemini: add support for AI Task generate image (#151880) --- .../ai_task.py | 123 +++++++++++++++++- .../const.py | 1 + .../entity.py | 11 +- .../conftest.py | 21 ++- .../snapshots/test_diagnostics.ambr | 4 + .../test_ai_task.py | 68 +++++++++- 6 files changed, 212 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/google_generative_ai_conversation/ai_task.py b/homeassistant/components/google_generative_ai_conversation/ai_task.py index 4ffca835fed21..003ca09947bf9 100644 --- a/homeassistant/components/google_generative_ai_conversation/ai_task.py +++ b/homeassistant/components/google_generative_ai_conversation/ai_task.py @@ -3,6 +3,10 @@ from __future__ import annotations from json import JSONDecodeError +from typing import TYPE_CHECKING + +from google.genai.errors import APIError +from google.genai.types import GenerateContentConfig, Part, PartUnionDict from homeassistant.components import ai_task, conversation from homeassistant.config_entries import ConfigEntry @@ -11,8 +15,17 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.util.json import json_loads -from .const import LOGGER -from .entity import ERROR_GETTING_RESPONSE, GoogleGenerativeAILLMBaseEntity +from .const import CONF_CHAT_MODEL, CONF_RECOMMENDED, LOGGER, RECOMMENDED_IMAGE_MODEL +from .entity import ( + ERROR_GETTING_RESPONSE, + GoogleGenerativeAILLMBaseEntity, + async_prepare_files_for_prompt, +) + +if TYPE_CHECKING: + from homeassistant.config_entries import ConfigSubentry + + from . import GoogleGenerativeAIConfigEntry async def async_setup_entry( @@ -37,10 +50,22 @@ class GoogleGenerativeAITaskEntity( ): """Google Generative AI AI Task entity.""" - _attr_supported_features = ( - ai_task.AITaskEntityFeature.GENERATE_DATA - | ai_task.AITaskEntityFeature.SUPPORT_ATTACHMENTS - ) + def __init__( + self, + entry: GoogleGenerativeAIConfigEntry, + subentry: ConfigSubentry, + ) -> None: + """Initialize the entity.""" + super().__init__(entry, subentry) + self._attr_supported_features = ( + ai_task.AITaskEntityFeature.GENERATE_DATA + | ai_task.AITaskEntityFeature.SUPPORT_ATTACHMENTS + ) + + if subentry.data.get(CONF_RECOMMENDED) or "-image" in subentry.data.get( + CONF_CHAT_MODEL, "" + ): + self._attr_supported_features |= ai_task.AITaskEntityFeature.GENERATE_IMAGE async def _async_generate_data( self, @@ -79,3 +104,89 @@ async def _async_generate_data( conversation_id=chat_log.conversation_id, data=data, ) + + async def _async_generate_image( + self, + task: ai_task.GenImageTask, + chat_log: conversation.ChatLog, + ) -> ai_task.GenImageTaskResult: + """Handle a generate image task.""" + # Get the user prompt from the chat log + user_message = chat_log.content[-1] + assert isinstance(user_message, conversation.UserContent) + + model = self.subentry.data.get(CONF_CHAT_MODEL, RECOMMENDED_IMAGE_MODEL) + prompt_parts: list[PartUnionDict] = [user_message.content] + if user_message.attachments: + prompt_parts.extend( + await async_prepare_files_for_prompt( + self.hass, + self._genai_client, + [a.path for a in user_message.attachments], + ) + ) + + try: + response = await self._genai_client.aio.models.generate_content( + model=model, + contents=prompt_parts, + config=GenerateContentConfig( + response_modalities=["TEXT", "IMAGE"], + ), + ) + except (APIError, ValueError) as err: + LOGGER.error("Error generating image: %s", err) + raise HomeAssistantError(f"Error generating image: {err}") from err + + if response.prompt_feedback: + raise HomeAssistantError( + f"Error generating content due to content violations, reason: {response.prompt_feedback.block_reason_message}" + ) + + if ( + not response.candidates + or not response.candidates[0].content + or not response.candidates[0].content.parts + ): + raise HomeAssistantError("Unknown error generating image") + + # Parse response + response_text = "" + response_image: Part | None = None + for part in response.candidates[0].content.parts: + if ( + part.inline_data + and part.inline_data.data + and part.inline_data.mime_type + and part.inline_data.mime_type.startswith("image/") + ): + if response_image is None: + response_image = part + else: + LOGGER.warning("Prompt generated multiple images") + elif isinstance(part.text, str) and not part.thought: + response_text += part.text + + if response_image is None: + raise HomeAssistantError("Response did not include image") + + assert response_image.inline_data is not None + assert response_image.inline_data.data is not None + assert response_image.inline_data.mime_type is not None + + image_data = response_image.inline_data.data + mime_type = response_image.inline_data.mime_type + + chat_log.async_add_assistant_content_without_tools( + conversation.AssistantContent( + agent_id=self.entity_id, + content=response_text, + ) + ) + + return ai_task.GenImageTaskResult( + image_data=image_data, + conversation_id=chat_log.conversation_id, + mime_type=mime_type, + model=model.partition("/")[-1], + ) diff --git a/homeassistant/components/google_generative_ai_conversation/const.py b/homeassistant/components/google_generative_ai_conversation/const.py index ba7af5147c5d1..1960e2bffdc8b 100644 --- a/homeassistant/components/google_generative_ai_conversation/const.py +++ b/homeassistant/components/google_generative_ai_conversation/const.py @@ -23,6 +23,7 @@ RECOMMENDED_CHAT_MODEL = "models/gemini-2.5-flash" RECOMMENDED_STT_MODEL = RECOMMENDED_CHAT_MODEL RECOMMENDED_TTS_MODEL = "models/gemini-2.5-flash-preview-tts" +RECOMMENDED_IMAGE_MODEL = "models/gemini-2.5-flash-image-preview" CONF_TEMPERATURE = "temperature" RECOMMENDED_TEMPERATURE = 1.0 CONF_TOP_P = "top_p" diff --git a/homeassistant/components/google_generative_ai_conversation/entity.py b/homeassistant/components/google_generative_ai_conversation/entity.py index 90c144530e07f..c9364603b7906 100644 --- a/homeassistant/components/google_generative_ai_conversation/entity.py +++ b/homeassistant/components/google_generative_ai_conversation/entity.py @@ -448,12 +448,13 @@ async def _async_handle_chat_log( assert isinstance(user_message, conversation.UserContent) chat_request: list[PartUnionDict] = [user_message.content] if user_message.attachments: - files = await async_prepare_files_for_prompt( - self.hass, - self._genai_client, - [a.path for a in user_message.attachments], + chat_request.extend( + await async_prepare_files_for_prompt( + self.hass, + self._genai_client, + [a.path for a in user_message.attachments], + ) ) - chat_request = [*chat_request, *files] # To prevent infinite loops, we limit the number of iterations for _iteration in range(MAX_TOOL_ITERATIONS): diff --git a/tests/components/google_generative_ai_conversation/conftest.py b/tests/components/google_generative_ai_conversation/conftest.py index b19482957b24f..6c5d70139e289 100644 --- a/tests/components/google_generative_ai_conversation/conftest.py +++ b/tests/components/google_generative_ai_conversation/conftest.py @@ -11,6 +11,10 @@ DEFAULT_CONVERSATION_NAME, DEFAULT_STT_NAME, DEFAULT_TTS_NAME, + RECOMMENDED_AI_TASK_OPTIONS, + RECOMMENDED_CONVERSATION_OPTIONS, + RECOMMENDED_STT_OPTIONS, + RECOMMENDED_TTS_OPTIONS, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_LLM_HASS_API @@ -34,28 +38,28 @@ def mock_config_entry(hass: HomeAssistant) -> MockConfigEntry: minor_version=3, subentries_data=[ { - "data": {}, + "data": RECOMMENDED_CONVERSATION_OPTIONS, "subentry_type": "conversation", "title": DEFAULT_CONVERSATION_NAME, "subentry_id": "ulid-conversation", "unique_id": None, }, { - "data": {}, + "data": RECOMMENDED_STT_OPTIONS, "subentry_type": "stt", "title": DEFAULT_STT_NAME, "subentry_id": "ulid-stt", "unique_id": None, }, { - "data": {}, + "data": RECOMMENDED_TTS_OPTIONS, "subentry_type": "tts", "title": DEFAULT_TTS_NAME, "subentry_id": "ulid-tts", "unique_id": None, }, { - "data": {}, + "data": RECOMMENDED_AI_TASK_OPTIONS, "subentry_type": "ai_task_data", "title": DEFAULT_AI_TASK_NAME, "subentry_id": "ulid-ai-task", @@ -143,3 +147,12 @@ async def mock_generator(stream): def mock_send_message_stream(mock_chat_create) -> Generator[AsyncMock]: """Mock stream response.""" return mock_chat_create.return_value.send_message_stream + + +@pytest.fixture +def mock_generate_content() -> Generator[AsyncMock]: + """Mock generate_content response.""" + with patch( + "google.genai.models.AsyncModels.generate_content", + ) as mock: + yield mock diff --git a/tests/components/google_generative_ai_conversation/snapshots/test_diagnostics.ambr b/tests/components/google_generative_ai_conversation/snapshots/test_diagnostics.ambr index bceb12a925688..d559a7d907edd 100644 --- a/tests/components/google_generative_ai_conversation/snapshots/test_diagnostics.ambr +++ b/tests/components/google_generative_ai_conversation/snapshots/test_diagnostics.ambr @@ -9,6 +9,7 @@ 'subentries': dict({ 'ulid-ai-task': dict({ 'data': dict({ + 'recommended': True, }), 'subentry_id': 'ulid-ai-task', 'subentry_type': 'ai_task_data', @@ -36,6 +37,8 @@ }), 'ulid-stt': dict({ 'data': dict({ + 'prompt': 'Transcribe the attached audio', + 'recommended': True, }), 'subentry_id': 'ulid-stt', 'subentry_type': 'stt', @@ -44,6 +47,7 @@ }), 'ulid-tts': dict({ 'data': dict({ + 'recommended': True, }), 'subentry_id': 'ulid-tts', 'subentry_type': 'tts', diff --git a/tests/components/google_generative_ai_conversation/test_ai_task.py b/tests/components/google_generative_ai_conversation/test_ai_task.py index 6326bd94ad9b9..11e6864d31276 100644 --- a/tests/components/google_generative_ai_conversation/test_ai_task.py +++ b/tests/components/google_generative_ai_conversation/test_ai_task.py @@ -1,13 +1,16 @@ """Test AI Task platform of Google Generative AI Conversation integration.""" from pathlib import Path -from unittest.mock import AsyncMock, patch +from unittest.mock import AsyncMock, Mock, patch from google.genai.types import File, FileState, GenerateContentResponse import pytest import voluptuous as vol from homeassistant.components import ai_task, media_source +from homeassistant.components.google_generative_ai_conversation.const import ( + RECOMMENDED_IMAGE_MODEL, +) from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er, selector @@ -216,3 +219,66 @@ async def test_generate_data( instructions="Test prompt", structure=vol.Schema({vol.Required("bla"): str}), ) + + +@pytest.mark.usefixtures("mock_init_component") +async def test_generate_image( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_generate_content: AsyncMock, + entity_registry: er.EntityRegistry, +) -> None: + """Test AI Task image generation.""" + mock_image_data = b"fake_image_data" + mock_generate_content.return_value = Mock( + text="Here is your generated image", + prompt_feedback=None, + candidates=[ + Mock( + content=Mock( + parts=[ + Mock( + text="Here is your generated image", + inline_data=None, + thought=False, + ), + Mock( + inline_data=Mock( + data=mock_image_data, mime_type="image/png" + ), + text=None, + thought=False, + ), + ] + ) + ) + ], + ) + + assert hass.data[ai_task.DATA_IMAGES] == {} + + result = await ai_task.async_generate_image( + hass, + task_name="Test Task", + entity_id="ai_task.google_ai_task", + instructions="Generate a test image", + ) + + assert result["height"] is None + assert result["width"] is None + assert result["revised_prompt"] == "Generate a test image" + assert result["mime_type"] == "image/png" + assert result["model"] == RECOMMENDED_IMAGE_MODEL.partition("/")[-1] + + assert len(hass.data[ai_task.DATA_IMAGES]) == 1 + image_data = next(iter(hass.data[ai_task.DATA_IMAGES].values())) + assert image_data.data == mock_image_data + assert image_data.mime_type == "image/png" + assert image_data.title == "Generate a test image" + + # Verify that generate_content was called with correct parameters + assert mock_generate_content.called + call_args = mock_generate_content.call_args + assert call_args.kwargs["model"] == RECOMMENDED_IMAGE_MODEL + assert call_args.kwargs["contents"] == ["Generate a test image"] + assert call_args.kwargs["config"].response_modalities == ["TEXT", "IMAGE"] From 2e2b9483df3a36ee492b0f98ebde95ab43c3a9a6 Mon Sep 17 00:00:00 2001 From: HarvsG <11440490+HarvsG@users.noreply.github.com> Date: Mon, 8 Sep 2025 21:17:29 +0100 Subject: [PATCH 14/15] Improve efficiency of config_entries `_async_abort_entries_match()` (#148344) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Abílio Costa Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- homeassistant/config_entries.py | 4 +++- tests/test_config_entries.py | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 37b4fbe60e6d3..65d1a57643416 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -2786,7 +2786,9 @@ def _async_abort_entries_match( Requires `already_configured` in strings.json in user visible flows. """ if match_dict is None: - match_dict = {} # Match any entry + if other_entries: + raise data_entry_flow.AbortFlow("already_configured") # Match any entry + return for entry in other_entries: options_items = entry.options.items() data_items = entry.data.items() diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index a051e09066e5a..7d9509a46fabf 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -5334,6 +5334,19 @@ async def async_step_user(self, user_input=None): assert result["type"] == FlowResultType.ABORT assert result["reason"] == reason + # For a domain with no entries, there should never be a match + mock_integration(hass, MockModule("not_comp", async_setup_entry=mock_setup_entry)) + mock_platform(hass, "not_comp.config_flow", None) + + with mock_config_flow("not_comp", TestFlow), mock_config_flow("invalid_flow", 5): + result = await manager.flow.async_init( + "not_comp", context={"source": config_entries.SOURCE_USER} + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "no_match" + @pytest.mark.parametrize( ("matchers", "reason"), From cecae10a1536c68a9ca8e8fb39a6a8d85eee5172 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Tue, 9 Sep 2025 00:23:26 +0300 Subject: [PATCH 15/15] Bump aioshelly to 13.9.0 (#151943) --- homeassistant/components/shelly/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/shelly/manifest.json b/homeassistant/components/shelly/manifest.json index f2ecb1adb81c7..119f2b95a7e86 100644 --- a/homeassistant/components/shelly/manifest.json +++ b/homeassistant/components/shelly/manifest.json @@ -9,7 +9,7 @@ "iot_class": "local_push", "loggers": ["aioshelly"], "quality_scale": "silver", - "requirements": ["aioshelly==13.8.0"], + "requirements": ["aioshelly==13.9.0"], "zeroconf": [ { "type": "_http._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index 2f19db17d55a5..f716de69367a6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -384,7 +384,7 @@ aioruuvigateway==0.1.0 aiosenz==1.0.0 # homeassistant.components.shelly -aioshelly==13.8.0 +aioshelly==13.9.0 # homeassistant.components.skybell aioskybell==22.7.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 96f8b713957dc..11f6da200f6c6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -366,7 +366,7 @@ aioruuvigateway==0.1.0 aiosenz==1.0.0 # homeassistant.components.shelly -aioshelly==13.8.0 +aioshelly==13.9.0 # homeassistant.components.skybell aioskybell==22.7.0