From 624baebbaa2c4de2f636f61d17597c6f7270f1fc Mon Sep 17 00:00:00 2001
From: Pete Sage <76050312+PeteRager@users.noreply.github.com>
Date: Tue, 7 May 2024 04:08:12 -0400
Subject: [PATCH 01/19] Fix Sonos select_source timeout error (#115640)
---
.../components/sonos/media_player.py | 12 +-
homeassistant/components/sonos/strings.json | 5 +
tests/components/sonos/conftest.py | 15 +-
.../sonos/fixtures/sonos_favorites.json | 38 +++++
tests/components/sonos/test_media_player.py | 159 +++++++++++++++++-
5 files changed, 222 insertions(+), 7 deletions(-)
create mode 100644 tests/components/sonos/fixtures/sonos_favorites.json
diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py
index 35c6be3fa6b45f..e9fbb152b7a141 100644
--- a/homeassistant/components/sonos/media_player.py
+++ b/homeassistant/components/sonos/media_player.py
@@ -39,7 +39,7 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TIME
from homeassistant.core import HomeAssistant, ServiceCall, callback
-from homeassistant.exceptions import HomeAssistantError
+from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import config_validation as cv, entity_platform, service
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@@ -432,7 +432,13 @@ def _play_favorite_by_name(self, name: str) -> None:
fav = [fav for fav in self.speaker.favorites if fav.title == name]
if len(fav) != 1:
- return
+ raise ServiceValidationError(
+ translation_domain=SONOS_DOMAIN,
+ translation_key="invalid_favorite",
+ translation_placeholders={
+ "name": name,
+ },
+ )
src = fav.pop()
self._play_favorite(src)
@@ -445,7 +451,7 @@ def _play_favorite(self, favorite: DidlFavorite) -> None:
MUSIC_SRC_RADIO,
MUSIC_SRC_LINE_IN,
]:
- soco.play_uri(uri, title=favorite.title)
+ soco.play_uri(uri, title=favorite.title, timeout=LONG_SERVICE_TIMEOUT)
else:
soco.clear_queue()
soco.add_to_queue(favorite.reference, timeout=LONG_SERVICE_TIMEOUT)
diff --git a/homeassistant/components/sonos/strings.json b/homeassistant/components/sonos/strings.json
index 6f45195c46bec8..6521302b0077ef 100644
--- a/homeassistant/components/sonos/strings.json
+++ b/homeassistant/components/sonos/strings.json
@@ -173,5 +173,10 @@
}
}
}
+ },
+ "exceptions": {
+ "invalid_favorite": {
+ "message": "Could not find a Sonos favorite: {name}"
+ }
}
}
diff --git a/tests/components/sonos/conftest.py b/tests/components/sonos/conftest.py
index 0eb9b497fbd479..15f371f272c9d4 100644
--- a/tests/components/sonos/conftest.py
+++ b/tests/components/sonos/conftest.py
@@ -9,6 +9,7 @@
import pytest
from soco import SoCo
from soco.alarms import Alarms
+from soco.data_structures import DidlFavorite, SearchResult
from soco.events_base import Event as SonosEvent
from homeassistant.components import ssdp, zeroconf
@@ -17,7 +18,7 @@
from homeassistant.const import CONF_HOSTS
from homeassistant.core import HomeAssistant
-from tests.common import MockConfigEntry, load_fixture
+from tests.common import MockConfigEntry, load_fixture, load_json_value_fixture
class SonosMockEventListener:
@@ -304,6 +305,14 @@ def config_fixture():
return {DOMAIN: {MP_DOMAIN: {CONF_HOSTS: ["192.168.42.2"]}}}
+@pytest.fixture(name="sonos_favorites")
+def sonos_favorites_fixture() -> SearchResult:
+ """Create sonos favorites fixture."""
+ favorites = load_json_value_fixture("sonos_favorites.json", "sonos")
+ favorite_list = [DidlFavorite.from_dict(fav) for fav in favorites]
+ return SearchResult(favorite_list, "favorites", 3, 3, 1)
+
+
class MockMusicServiceItem:
"""Mocks a Soco MusicServiceItem."""
@@ -408,10 +417,10 @@ def mock_get_music_library_information(
@pytest.fixture(name="music_library")
-def music_library_fixture():
+def music_library_fixture(sonos_favorites: SearchResult) -> Mock:
"""Create music_library fixture."""
music_library = MagicMock()
- music_library.get_sonos_favorites.return_value.update_id = 1
+ music_library.get_sonos_favorites.return_value = sonos_favorites
music_library.browse_by_idstring = mock_browse_by_idstring
music_library.get_music_library_information = mock_get_music_library_information
return music_library
diff --git a/tests/components/sonos/fixtures/sonos_favorites.json b/tests/components/sonos/fixtures/sonos_favorites.json
new file mode 100644
index 00000000000000..21ee68f4872aa6
--- /dev/null
+++ b/tests/components/sonos/fixtures/sonos_favorites.json
@@ -0,0 +1,38 @@
+[
+ {
+ "title": "66 - Watercolors",
+ "parent_id": "FV:2",
+ "item_id": "FV:2/4",
+ "resource_meta_data": "- 66 - Watercolorsobject.item.audioItem.audioBroadcastSA_RINCON9479_X_#Svc9479-99999999-Token
",
+ "resources": [
+ {
+ "uri": "x-sonosapi-hls:Api%3atune%3aliveAudio%3ajazzcafe%3aetc",
+ "protocol_info": "a:b:c:d"
+ }
+ ]
+ },
+ {
+ "title": "James Taylor Radio",
+ "parent_id": "FV:2",
+ "item_id": "FV:2/13",
+ "resource_meta_data": "- James Taylor Radioobject.item.audioItem.audioBroadcast.#stationSA_RINCON60423_X_#Svc60423-99999999-Token
",
+ "resources": [
+ {
+ "uri": "x-sonosapi-radio:ST%3aetc",
+ "protocol_info": "a:b:c:d"
+ }
+ ]
+ },
+ {
+ "title": "1984",
+ "parent_id": "FV:2",
+ "item_id": "FV:2/8",
+ "resource_meta_data": "- 1984object.container.album.musicAlbumRINCON_AssociatedZPUDN
",
+ "resources": [
+ {
+ "uri": "x-rincon-playlist:RINCON_test#A:ALBUMARTIST/Aerosmith/1984",
+ "protocol_info": "a:b:c:d"
+ }
+ ]
+ }
+]
diff --git a/tests/components/sonos/test_media_player.py b/tests/components/sonos/test_media_player.py
index 976d3480429359..9fb8444a6963fb 100644
--- a/tests/components/sonos/test_media_player.py
+++ b/tests/components/sonos/test_media_player.py
@@ -1,6 +1,7 @@
"""Tests for the Sonos Media Player platform."""
import logging
+from typing import Any
import pytest
@@ -9,10 +10,15 @@
SERVICE_PLAY_MEDIA,
MediaPlayerEnqueue,
)
-from homeassistant.components.media_player.const import ATTR_MEDIA_ENQUEUE
+from homeassistant.components.media_player.const import (
+ ATTR_MEDIA_ENQUEUE,
+ SERVICE_SELECT_SOURCE,
+)
+from homeassistant.components.sonos.const import SOURCE_LINEIN, SOURCE_TV
from homeassistant.components.sonos.media_player import LONG_SERVICE_TIMEOUT
from homeassistant.const import STATE_IDLE
from homeassistant.core import HomeAssistant
+from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.device_registry import (
CONNECTION_NETWORK_MAC,
CONNECTION_UPNP,
@@ -272,3 +278,154 @@ async def test_play_media_music_library_playlist_dne(
assert soco_mock.play_uri.call_count == 0
assert media_content_id in caplog.text
assert "playlist" in caplog.text
+
+
+@pytest.mark.parametrize(
+ ("source", "result"),
+ [
+ (
+ SOURCE_LINEIN,
+ {
+ "switch_to_line_in": 1,
+ },
+ ),
+ (
+ SOURCE_TV,
+ {
+ "switch_to_tv": 1,
+ },
+ ),
+ ],
+)
+async def test_select_source_line_in_tv(
+ hass: HomeAssistant,
+ soco_factory: SoCoMockFactory,
+ async_autosetup_sonos,
+ source: str,
+ result: dict[str, Any],
+) -> None:
+ """Test the select_source method with a variety of inputs."""
+ soco_mock = soco_factory.mock_list.get("192.168.42.2")
+ await hass.services.async_call(
+ MP_DOMAIN,
+ SERVICE_SELECT_SOURCE,
+ {
+ "entity_id": "media_player.zone_a",
+ "source": source,
+ },
+ blocking=True,
+ )
+ assert soco_mock.switch_to_line_in.call_count == result.get("switch_to_line_in", 0)
+ assert soco_mock.switch_to_tv.call_count == result.get("switch_to_tv", 0)
+
+
+@pytest.mark.parametrize(
+ ("source", "result"),
+ [
+ (
+ "James Taylor Radio",
+ {
+ "play_uri": 1,
+ "play_uri_uri": "x-sonosapi-radio:ST%3aetc",
+ "play_uri_title": "James Taylor Radio",
+ },
+ ),
+ (
+ "66 - Watercolors",
+ {
+ "play_uri": 1,
+ "play_uri_uri": "x-sonosapi-hls:Api%3atune%3aliveAudio%3ajazzcafe%3aetc",
+ "play_uri_title": "66 - Watercolors",
+ },
+ ),
+ ],
+)
+async def test_select_source_play_uri(
+ hass: HomeAssistant,
+ soco_factory: SoCoMockFactory,
+ async_autosetup_sonos,
+ source: str,
+ result: dict[str, Any],
+) -> None:
+ """Test the select_source method with a variety of inputs."""
+ soco_mock = soco_factory.mock_list.get("192.168.42.2")
+ await hass.services.async_call(
+ MP_DOMAIN,
+ SERVICE_SELECT_SOURCE,
+ {
+ "entity_id": "media_player.zone_a",
+ "source": source,
+ },
+ blocking=True,
+ )
+ assert soco_mock.play_uri.call_count == result.get("play_uri")
+ soco_mock.play_uri.assert_called_with(
+ result.get("play_uri_uri"),
+ title=result.get("play_uri_title"),
+ timeout=LONG_SERVICE_TIMEOUT,
+ )
+
+
+@pytest.mark.parametrize(
+ ("source", "result"),
+ [
+ (
+ "1984",
+ {
+ "add_to_queue": 1,
+ "add_to_queue_item_id": "A:ALBUMARTIST/Aerosmith/1984",
+ "clear_queue": 1,
+ "play_from_queue": 1,
+ },
+ ),
+ ],
+)
+async def test_select_source_play_queue(
+ hass: HomeAssistant,
+ soco_factory: SoCoMockFactory,
+ async_autosetup_sonos,
+ source: str,
+ result: dict[str, Any],
+) -> None:
+ """Test the select_source method with a variety of inputs."""
+ soco_mock = soco_factory.mock_list.get("192.168.42.2")
+ await hass.services.async_call(
+ MP_DOMAIN,
+ SERVICE_SELECT_SOURCE,
+ {
+ "entity_id": "media_player.zone_a",
+ "source": source,
+ },
+ blocking=True,
+ )
+ assert soco_mock.clear_queue.call_count == result.get("clear_queue")
+ assert soco_mock.add_to_queue.call_count == result.get("add_to_queue")
+ assert soco_mock.add_to_queue.call_args_list[0].args[0].item_id == result.get(
+ "add_to_queue_item_id"
+ )
+ assert (
+ soco_mock.add_to_queue.call_args_list[0].kwargs["timeout"]
+ == LONG_SERVICE_TIMEOUT
+ )
+ assert soco_mock.play_from_queue.call_count == result.get("play_from_queue")
+ soco_mock.play_from_queue.assert_called_with(0)
+
+
+async def test_select_source_error(
+ hass: HomeAssistant,
+ soco_factory: SoCoMockFactory,
+ async_autosetup_sonos,
+) -> None:
+ """Test the select_source method with a variety of inputs."""
+ with pytest.raises(ServiceValidationError) as sve:
+ await hass.services.async_call(
+ MP_DOMAIN,
+ SERVICE_SELECT_SOURCE,
+ {
+ "entity_id": "media_player.zone_a",
+ "source": "invalid_source",
+ },
+ blocking=True,
+ )
+ assert "invalid_source" in str(sve.value)
+ assert "Could not find a Sonos favorite" in str(sve.value)
From 57861dc091ec3c5aab0096f53a8461f25d7ada1e Mon Sep 17 00:00:00 2001
From: "Mr. Bubbles"
Date: Tue, 7 May 2024 21:10:04 +0200
Subject: [PATCH 02/19] Update strings for Bring notification service (#116181)
update translations
---
homeassistant/components/bring/strings.json | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/homeassistant/components/bring/strings.json b/homeassistant/components/bring/strings.json
index e6df885cbbc21c..5deb0759c174ff 100644
--- a/homeassistant/components/bring/strings.json
+++ b/homeassistant/components/bring/strings.json
@@ -60,8 +60,8 @@
"description": "Type of push notification to send to list members."
},
"item": {
- "name": "Item (Required if message type `Breaking news` selected)",
- "description": "Item name to include in a breaking news message e.g. `Breaking news - Please get cilantro!`"
+ "name": "Article (Required if message type `Urgent Message` selected)",
+ "description": "Article name to include in an urgent message e.g. `Urgent Message - Please buy Cilantro urgently`"
}
}
}
@@ -69,10 +69,10 @@
"selector": {
"notification_type_selector": {
"options": {
- "going_shopping": "I'm going shopping! - Last chance for adjustments",
- "changed_list": "List changed - Check it out",
- "shopping_done": "Shopping done - you can relax",
- "urgent_message": "Breaking news - Please get `item`!"
+ "going_shopping": "I'm going shopping! - Last chance to make changes",
+ "changed_list": "List updated - Take a look at the articles",
+ "shopping_done": "Shopping done - The fridge is well stocked",
+ "urgent_message": "Urgent Message - Please buy `Article name` urgently"
}
}
}
From fdc59547e0f02a7799657dffecd12df969f44d01 Mon Sep 17 00:00:00 2001
From: Matrix
Date: Tue, 7 May 2024 13:51:10 +0800
Subject: [PATCH 03/19] Bump Yolink api to 0.4.4 (#116967)
---
homeassistant/components/yolink/manifest.json | 2 +-
requirements_all.txt | 2 +-
requirements_test_all.txt | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/homeassistant/components/yolink/manifest.json b/homeassistant/components/yolink/manifest.json
index b7bd1d4784f9ae..5353d5d5b8c9d5 100644
--- a/homeassistant/components/yolink/manifest.json
+++ b/homeassistant/components/yolink/manifest.json
@@ -6,5 +6,5 @@
"dependencies": ["auth", "application_credentials"],
"documentation": "https://www.home-assistant.io/integrations/yolink",
"iot_class": "cloud_push",
- "requirements": ["yolink-api==0.4.3"]
+ "requirements": ["yolink-api==0.4.4"]
}
diff --git a/requirements_all.txt b/requirements_all.txt
index e4c84b11ab82f6..f188c7ea248011 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -2914,7 +2914,7 @@ yeelight==0.7.14
yeelightsunflower==0.0.10
# homeassistant.components.yolink
-yolink-api==0.4.3
+yolink-api==0.4.4
# homeassistant.components.youless
youless-api==1.0.1
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index e9dc44b3765c9b..9bec4e50de4929 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -2264,7 +2264,7 @@ yalexs==3.0.1
yeelight==0.7.14
# homeassistant.components.yolink
-yolink-api==0.4.3
+yolink-api==0.4.4
# homeassistant.components.youless
youless-api==1.0.1
From bee518dc78cb7c1588e9c54df71bdcae18b86d82 Mon Sep 17 00:00:00 2001
From: Marc Mueller <30130371+cdce8p@users.noreply.github.com>
Date: Tue, 7 May 2024 13:56:11 +0200
Subject: [PATCH 04/19] Update jinja2 to 3.1.4 (#116986)
---
homeassistant/package_constraints.txt | 2 +-
pyproject.toml | 2 +-
requirements.txt | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt
index 0f69f7d63c9682..13ac6119f66c6b 100644
--- a/homeassistant/package_constraints.txt
+++ b/homeassistant/package_constraints.txt
@@ -36,7 +36,7 @@ home-assistant-frontend==20240501.1
home-assistant-intents==2024.4.24
httpx==0.27.0
ifaddr==0.2.0
-Jinja2==3.1.3
+Jinja2==3.1.4
lru-dict==1.3.0
mutagen==1.47.0
orjson==3.9.15
diff --git a/pyproject.toml b/pyproject.toml
index 887083304cf8da..8fb7839c628196 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -46,7 +46,7 @@ dependencies = [
"httpx==0.27.0",
"home-assistant-bluetooth==1.12.0",
"ifaddr==0.2.0",
- "Jinja2==3.1.3",
+ "Jinja2==3.1.4",
"lru-dict==1.3.0",
"PyJWT==2.8.0",
# PyJWT has loose dependency. We want the latest one.
diff --git a/requirements.txt b/requirements.txt
index df001251a04849..9d0cd618b2eeee 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -22,7 +22,7 @@ hass-nabucasa==0.78.0
httpx==0.27.0
home-assistant-bluetooth==1.12.0
ifaddr==0.2.0
-Jinja2==3.1.3
+Jinja2==3.1.4
lru-dict==1.3.0
PyJWT==2.8.0
cryptography==42.0.5
From 1a13e1d024aca7bdf1ca95ce9356217310896300 Mon Sep 17 00:00:00 2001
From: "J. Nick Koston"
Date: Tue, 7 May 2024 14:41:31 -0500
Subject: [PATCH 05/19] Simplify MQTT subscribe debouncer execution (#117006)
---
homeassistant/components/mqtt/client.py | 19 +++++++------------
tests/components/mqtt/test_init.py | 22 +++++++++++-----------
tests/components/mqtt/test_mixins.py | 3 +++
3 files changed, 21 insertions(+), 23 deletions(-)
diff --git a/homeassistant/components/mqtt/client.py b/homeassistant/components/mqtt/client.py
index 4b05442d71b13d..2ca17f012e4ae9 100644
--- a/homeassistant/components/mqtt/client.py
+++ b/homeassistant/components/mqtt/client.py
@@ -317,7 +317,7 @@ def __init__(
self._loop = asyncio.get_running_loop()
self._timeout = timeout
self._callback = callback_job
- self._task: asyncio.Future | None = None
+ self._task: asyncio.Task | None = None
self._timer: asyncio.TimerHandle | None = None
def set_timeout(self, timeout: float) -> None:
@@ -332,28 +332,23 @@ async def _async_job(self) -> None:
_LOGGER.error("%s", ha_error)
@callback
- def _async_task_done(self, task: asyncio.Future) -> None:
+ def _async_task_done(self, task: asyncio.Task) -> None:
"""Handle task done."""
self._task = None
@callback
- def _async_execute(self) -> None:
+ def async_execute(self) -> asyncio.Task:
"""Execute the job."""
if self._task:
# Task already running,
# so we schedule another run
self.async_schedule()
- return
+ return self._task
self._async_cancel_timer()
self._task = create_eager_task(self._async_job())
self._task.add_done_callback(self._async_task_done)
-
- async def async_fire(self) -> None:
- """Execute the job immediately."""
- if self._task:
- await self._task
- self._async_execute()
+ return self._task
@callback
def _async_cancel_timer(self) -> None:
@@ -368,7 +363,7 @@ def async_schedule(self) -> None:
# We want to reschedule the timer in the future
# every time this is called.
self._async_cancel_timer()
- self._timer = self._loop.call_later(self._timeout, self._async_execute)
+ self._timer = self._loop.call_later(self._timeout, self.async_execute)
async def async_cleanup(self) -> None:
"""Cleanup any pending task."""
@@ -883,7 +878,7 @@ async def _async_resubscribe_and_publish_birth_message(
await self._discovery_cooldown() # Wait for MQTT discovery to cool down
# Update subscribe cooldown period to a shorter time
# and make sure we flush the debouncer
- await self._subscribe_debouncer.async_fire()
+ await self._subscribe_debouncer.async_execute()
self._subscribe_debouncer.set_timeout(SUBSCRIBE_COOLDOWN)
await self.async_publish(
topic=birth_message.topic,
diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py
index a1264b52739a09..b7998274aa0c6a 100644
--- a/tests/components/mqtt/test_init.py
+++ b/tests/components/mqtt/test_init.py
@@ -2589,19 +2589,19 @@ def wait_birth(msg: ReceiveMessage) -> None:
mqtt_client_mock.on_connect(None, None, 0, 0)
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
+ await mqtt.async_subscribe(hass, "topic/test", record_calls)
# We wait until we receive a birth message
await asyncio.wait_for(birth.wait(), 1)
- # Assert we already have subscribed at the client
- # for new config payloads at the time we the birth message is received
- assert ("homeassistant/+/+/config", 0) in help_all_subscribe_calls(
- mqtt_client_mock
- )
- assert ("homeassistant/+/+/+/config", 0) in help_all_subscribe_calls(
- mqtt_client_mock
- )
- mqtt_client_mock.publish.assert_called_with(
- "homeassistant/status", "online", 0, False
- )
+
+ # Assert we already have subscribed at the client
+ # for new config payloads at the time we the birth message is received
+ subscribe_calls = help_all_subscribe_calls(mqtt_client_mock)
+ assert ("homeassistant/+/+/config", 0) in subscribe_calls
+ assert ("homeassistant/+/+/+/config", 0) in subscribe_calls
+ mqtt_client_mock.publish.assert_called_with(
+ "homeassistant/status", "online", 0, False
+ )
+ assert ("topic/test", 0) in subscribe_calls
@pytest.mark.parametrize(
diff --git a/tests/components/mqtt/test_mixins.py b/tests/components/mqtt/test_mixins.py
index 2bcd663c243cfe..e46f0b56c15c27 100644
--- a/tests/components/mqtt/test_mixins.py
+++ b/tests/components/mqtt/test_mixins.py
@@ -335,6 +335,9 @@ async def test_default_entity_and_device_name(
# Assert that no issues ware registered
assert len(events) == 0
+ await hass.async_block_till_done()
+ # Assert that no issues ware registered
+ assert len(events) == 0
async def test_name_attribute_is_set_or_not(
From f34a0dc5ce760164aa00046ace3899162a9bc995 Mon Sep 17 00:00:00 2001
From: Jan Bouwhuis
Date: Tue, 7 May 2024 21:19:46 +0200
Subject: [PATCH 06/19] Log an exception mqtt client call back throws (#117028)
* Log an exception mqtt client call back throws
* Supress exceptions and add test
---
homeassistant/components/mqtt/client.py | 22 +++++++++++---
tests/components/mqtt/test_init.py | 39 ++++++++++++++++++++++++-
2 files changed, 56 insertions(+), 5 deletions(-)
diff --git a/homeassistant/components/mqtt/client.py b/homeassistant/components/mqtt/client.py
index 2ca17f012e4ae9..589113d3a9e9b6 100644
--- a/homeassistant/components/mqtt/client.py
+++ b/homeassistant/components/mqtt/client.py
@@ -492,6 +492,9 @@ def init_client(self) -> None:
mqttc.on_subscribe = self._async_mqtt_on_callback
mqttc.on_unsubscribe = self._async_mqtt_on_callback
+ # suppress exceptions at callback
+ mqttc.suppress_exceptions = True
+
if will := self.conf.get(CONF_WILL_MESSAGE, DEFAULT_WILL):
will_message = PublishMessage(**will)
mqttc.will_set(
@@ -988,10 +991,21 @@ def _matching_subscriptions(self, topic: str) -> list[Subscription]:
def _async_mqtt_on_message(
self, _mqttc: mqtt.Client, _userdata: None, msg: mqtt.MQTTMessage
) -> None:
- topic = msg.topic
- # msg.topic is a property that decodes the topic to a string
- # every time it is accessed. Save the result to avoid
- # decoding the same topic multiple times.
+ try:
+ # msg.topic is a property that decodes the topic to a string
+ # every time it is accessed. Save the result to avoid
+ # decoding the same topic multiple times.
+ topic = msg.topic
+ except UnicodeDecodeError:
+ bare_topic: bytes = getattr(msg, "_topic")
+ _LOGGER.warning(
+ "Skipping received%s message on invalid topic %s (qos=%s): %s",
+ " retained" if msg.retain else "",
+ bare_topic,
+ msg.qos,
+ msg.payload[0:8192],
+ )
+ return
_LOGGER.debug(
"Received%s message on %s (qos=%s): %s",
" retained" if msg.retain else "",
diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py
index b7998274aa0c6a..ec7968ae46b43e 100644
--- a/tests/components/mqtt/test_init.py
+++ b/tests/components/mqtt/test_init.py
@@ -6,8 +6,9 @@
import json
import socket
import ssl
+import time
from typing import Any, TypedDict
-from unittest.mock import ANY, MagicMock, call, mock_open, patch
+from unittest.mock import ANY, MagicMock, Mock, call, mock_open, patch
from freezegun.api import FrozenDateTimeFactory
import paho.mqtt.client as paho_mqtt
@@ -938,6 +939,42 @@ async def test_receiving_non_utf8_message_gets_logged(
)
+async def test_receiving_message_with_non_utf8_topic_gets_logged(
+ hass: HomeAssistant,
+ mqtt_mock_entry: MqttMockHAClientGenerator,
+ record_calls: MessageCallbackType,
+ caplog: pytest.LogCaptureFixture,
+) -> None:
+ """Test receiving a non utf8 encoded topic."""
+ await mqtt_mock_entry()
+ await mqtt.async_subscribe(hass, "test-topic", record_calls)
+
+ # Local import to avoid processing MQTT modules when running a testcase
+ # which does not use MQTT.
+
+ # pylint: disable-next=import-outside-toplevel
+ from paho.mqtt.client import MQTTMessage
+
+ # pylint: disable-next=import-outside-toplevel
+ from homeassistant.components.mqtt.models import MqttData
+
+ msg = MQTTMessage(topic=b"tasmota/discovery/18FE34E0B760\xcc\x02")
+ msg.payload = b"Payload"
+ msg.qos = 2
+ msg.retain = True
+ msg.timestamp = time.monotonic()
+
+ mqtt_data: MqttData = hass.data["mqtt"]
+ assert mqtt_data.client
+ mqtt_data.client._async_mqtt_on_message(Mock(), None, msg)
+
+ assert (
+ "Skipping received retained message on invalid "
+ "topic b'tasmota/discovery/18FE34E0B760\\xcc\\x02' "
+ "(qos=2): b'Payload'" in caplog.text
+ )
+
+
async def test_all_subscriptions_run_when_decode_fails(
hass: HomeAssistant,
mqtt_mock_entry: MqttMockHAClientGenerator,
From 08ba5304feb801211c26f68c6f3e98da43b22b18 Mon Sep 17 00:00:00 2001
From: Chris Talkington
Date: Wed, 8 May 2024 08:38:44 -0500
Subject: [PATCH 07/19] Bump rokuecp to 0.19.3 (#117059)
---
homeassistant/components/roku/manifest.json | 2 +-
requirements_all.txt | 2 +-
requirements_test_all.txt | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/homeassistant/components/roku/manifest.json b/homeassistant/components/roku/manifest.json
index ce4513fb31661b..fa9823de172510 100644
--- a/homeassistant/components/roku/manifest.json
+++ b/homeassistant/components/roku/manifest.json
@@ -11,7 +11,7 @@
"iot_class": "local_polling",
"loggers": ["rokuecp"],
"quality_scale": "silver",
- "requirements": ["rokuecp==0.19.2"],
+ "requirements": ["rokuecp==0.19.3"],
"ssdp": [
{
"st": "roku:ecp",
diff --git a/requirements_all.txt b/requirements_all.txt
index f188c7ea248011..b7147c8f8ec5d5 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -2460,7 +2460,7 @@ rjpl==0.3.6
rocketchat-API==0.6.1
# homeassistant.components.roku
-rokuecp==0.19.2
+rokuecp==0.19.3
# homeassistant.components.romy
romy==0.0.10
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 9bec4e50de4929..2f84692c081489 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -1906,7 +1906,7 @@ rflink==0.0.66
ring-doorbell[listen]==0.8.11
# homeassistant.components.roku
-rokuecp==0.19.2
+rokuecp==0.19.3
# homeassistant.components.romy
romy==0.0.10
From 9e7e839f03acce12c687a0701be9feac617eb847 Mon Sep 17 00:00:00 2001
From: Arie Catsman <120491684+catsmanac@users.noreply.github.com>
Date: Wed, 8 May 2024 14:02:49 +0200
Subject: [PATCH 08/19] Bump pyenphase to 1.20.3 (#117061)
---
homeassistant/components/enphase_envoy/manifest.json | 2 +-
requirements_all.txt | 2 +-
requirements_test_all.txt | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/homeassistant/components/enphase_envoy/manifest.json b/homeassistant/components/enphase_envoy/manifest.json
index 597d326968d841..b3c117556bf16a 100644
--- a/homeassistant/components/enphase_envoy/manifest.json
+++ b/homeassistant/components/enphase_envoy/manifest.json
@@ -6,7 +6,7 @@
"documentation": "https://www.home-assistant.io/integrations/enphase_envoy",
"iot_class": "local_polling",
"loggers": ["pyenphase"],
- "requirements": ["pyenphase==1.20.1"],
+ "requirements": ["pyenphase==1.20.3"],
"zeroconf": [
{
"type": "_enphase-envoy._tcp.local."
diff --git a/requirements_all.txt b/requirements_all.txt
index b7147c8f8ec5d5..e39f08d66bf49b 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -1800,7 +1800,7 @@ pyefergy==22.1.1
pyegps==0.2.5
# homeassistant.components.enphase_envoy
-pyenphase==1.20.1
+pyenphase==1.20.3
# homeassistant.components.envisalink
pyenvisalink==4.6
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 2f84692c081489..2939c4e843ecff 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -1405,7 +1405,7 @@ pyefergy==22.1.1
pyegps==0.2.5
# homeassistant.components.enphase_envoy
-pyenphase==1.20.1
+pyenphase==1.20.3
# homeassistant.components.everlights
pyeverlights==0.1.0
From d40689024a6f389134c141a52eb3f0397040f9ad Mon Sep 17 00:00:00 2001
From: puddly <32534428+puddly@users.noreply.github.com>
Date: Wed, 8 May 2024 17:57:50 -0400
Subject: [PATCH 09/19] Add a missing `addon_name` placeholder to the
SkyConnect config flow (#117089)
---
.../components/homeassistant_sky_connect/config_flow.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/homeassistant/components/homeassistant_sky_connect/config_flow.py b/homeassistant/components/homeassistant_sky_connect/config_flow.py
index 9d0aa902cc41ff..a65aefe96f2ca6 100644
--- a/homeassistant/components/homeassistant_sky_connect/config_flow.py
+++ b/homeassistant/components/homeassistant_sky_connect/config_flow.py
@@ -95,7 +95,10 @@ async def _async_set_addon_config(
_LOGGER.error(err)
raise AbortFlow(
"addon_set_config_failed",
- description_placeholders=self._get_translation_placeholders(),
+ description_placeholders={
+ **self._get_translation_placeholders(),
+ "addon_name": addon_manager.addon_name,
+ },
) from err
async def _async_get_addon_info(self, addon_manager: AddonManager) -> AddonInfo:
From 82fab7df399f2f8d9091575ade306d4fadc9b093 Mon Sep 17 00:00:00 2001
From: mletenay
Date: Thu, 9 May 2024 00:08:08 +0200
Subject: [PATCH 10/19] Goodwe Increase max value of export limit to 200%
(#117090)
---
homeassistant/components/goodwe/number.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/homeassistant/components/goodwe/number.py b/homeassistant/components/goodwe/number.py
index fc8b3864ae9cb5..d54fb8d8d0cb08 100644
--- a/homeassistant/components/goodwe/number.py
+++ b/homeassistant/components/goodwe/number.py
@@ -63,7 +63,7 @@ def _get_setting_unit(inverter: Inverter, setting: str) -> str:
native_unit_of_measurement=PERCENTAGE,
native_step=1,
native_min_value=0,
- native_max_value=100,
+ native_max_value=200,
getter=lambda inv: inv.get_grid_export_limit(),
setter=lambda inv, val: inv.set_grid_export_limit(val),
filter=lambda inv: _get_setting_unit(inv, "grid_export_limit") == "%",
From 11f86d9e0b0e54a9ef85e0ede2d212cb7c3ee677 Mon Sep 17 00:00:00 2001
From: "J. Nick Koston"
Date: Wed, 8 May 2024 14:16:08 -0500
Subject: [PATCH 11/19] Improve config entry has already been setup error
message (#117091)
---
homeassistant/helpers/entity_component.py | 5 ++++-
tests/helpers/test_entity_component.py | 9 ++++++++-
2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py
index eb54d83e1dddfd..aae0e2058e4747 100644
--- a/homeassistant/helpers/entity_component.py
+++ b/homeassistant/helpers/entity_component.py
@@ -182,7 +182,10 @@ async def async_setup_entry(self, config_entry: ConfigEntry) -> bool:
key = config_entry.entry_id
if key in self._platforms:
- raise ValueError("Config entry has already been setup!")
+ raise ValueError(
+ f"Config entry {config_entry.title} ({key}) for "
+ f"{platform_type}.{self.domain} has already been setup!"
+ )
self._platforms[key] = self._async_init_entity_platform(
platform_type,
diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py
index 60d0774b549a8b..330876aae05661 100644
--- a/tests/helpers/test_entity_component.py
+++ b/tests/helpers/test_entity_component.py
@@ -3,6 +3,7 @@
from collections import OrderedDict
from datetime import timedelta
import logging
+import re
from unittest.mock import AsyncMock, Mock, patch
from freezegun import freeze_time
@@ -365,7 +366,13 @@ async def test_setup_entry_fails_duplicate(hass: HomeAssistant) -> None:
assert await component.async_setup_entry(entry)
- with pytest.raises(ValueError):
+ with pytest.raises(
+ ValueError,
+ match=re.escape(
+ f"Config entry Mock Title ({entry.entry_id}) for "
+ "entry_domain.test_domain has already been setup!"
+ ),
+ ):
await component.async_setup_entry(entry)
From b9ed2dab5faa5b1ddf29015376379f0017bb095f Mon Sep 17 00:00:00 2001
From: MatthewFlamm <39341281+MatthewFlamm@users.noreply.github.com>
Date: Wed, 8 May 2024 15:16:20 -0400
Subject: [PATCH 12/19] Fix nws blocking startup (#117094)
Co-authored-by: J. Nick Koston
---
homeassistant/components/nws/__init__.py | 70 ++++++++++++++++--------
1 file changed, 48 insertions(+), 22 deletions(-)
diff --git a/homeassistant/components/nws/__init__.py b/homeassistant/components/nws/__init__.py
index 840d4d917f731d..df8cb4c329c2ac 100644
--- a/homeassistant/components/nws/__init__.py
+++ b/homeassistant/components/nws/__init__.py
@@ -2,8 +2,10 @@
from __future__ import annotations
+from collections.abc import Awaitable, Callable
from dataclasses import dataclass
import datetime
+from functools import partial
import logging
from pynws import SimpleNWS, call_with_retry
@@ -58,36 +60,49 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
nws_data = SimpleNWS(latitude, longitude, api_key, client_session)
await nws_data.set_station(station)
- async def update_observation() -> None:
- """Retrieve recent observations."""
- await call_with_retry(
- nws_data.update_observation,
- RETRY_INTERVAL,
- RETRY_STOP,
- start_time=utcnow() - UPDATE_TIME_PERIOD,
- )
-
- async def update_forecast() -> None:
- """Retrieve twice-daily forecsat."""
- await call_with_retry(
+ def async_setup_update_observation(
+ retry_interval: datetime.timedelta | float,
+ retry_stop: datetime.timedelta | float,
+ ) -> Callable[[], Awaitable[None]]:
+ async def update_observation() -> None:
+ """Retrieve recent observations."""
+ await call_with_retry(
+ nws_data.update_observation,
+ retry_interval,
+ retry_stop,
+ start_time=utcnow() - UPDATE_TIME_PERIOD,
+ )
+
+ return update_observation
+
+ def async_setup_update_forecast(
+ retry_interval: datetime.timedelta | float,
+ retry_stop: datetime.timedelta | float,
+ ) -> Callable[[], Awaitable[None]]:
+ return partial(
+ call_with_retry,
nws_data.update_forecast,
- RETRY_INTERVAL,
- RETRY_STOP,
+ retry_interval,
+ retry_stop,
)
- async def update_forecast_hourly() -> None:
- """Retrieve hourly forecast."""
- await call_with_retry(
+ def async_setup_update_forecast_hourly(
+ retry_interval: datetime.timedelta | float,
+ retry_stop: datetime.timedelta | float,
+ ) -> Callable[[], Awaitable[None]]:
+ return partial(
+ call_with_retry,
nws_data.update_forecast_hourly,
- RETRY_INTERVAL,
- RETRY_STOP,
+ retry_interval,
+ retry_stop,
)
+ # Don't use retries in setup
coordinator_observation = TimestampDataUpdateCoordinator(
hass,
_LOGGER,
name=f"NWS observation station {station}",
- update_method=update_observation,
+ update_method=async_setup_update_observation(0, 0),
update_interval=DEFAULT_SCAN_INTERVAL,
request_refresh_debouncer=debounce.Debouncer(
hass, _LOGGER, cooldown=DEBOUNCE_TIME, immediate=True
@@ -98,7 +113,7 @@ async def update_forecast_hourly() -> None:
hass,
_LOGGER,
name=f"NWS forecast station {station}",
- update_method=update_forecast,
+ update_method=async_setup_update_forecast(0, 0),
update_interval=DEFAULT_SCAN_INTERVAL,
request_refresh_debouncer=debounce.Debouncer(
hass, _LOGGER, cooldown=DEBOUNCE_TIME, immediate=True
@@ -109,7 +124,7 @@ async def update_forecast_hourly() -> None:
hass,
_LOGGER,
name=f"NWS forecast hourly station {station}",
- update_method=update_forecast_hourly,
+ update_method=async_setup_update_forecast_hourly(0, 0),
update_interval=DEFAULT_SCAN_INTERVAL,
request_refresh_debouncer=debounce.Debouncer(
hass, _LOGGER, cooldown=DEBOUNCE_TIME, immediate=True
@@ -128,6 +143,17 @@ async def update_forecast_hourly() -> None:
await coordinator_forecast.async_refresh()
await coordinator_forecast_hourly.async_refresh()
+ # Use retries
+ coordinator_observation.update_method = async_setup_update_observation(
+ RETRY_INTERVAL, RETRY_STOP
+ )
+ coordinator_forecast.update_method = async_setup_update_forecast(
+ RETRY_INTERVAL, RETRY_STOP
+ )
+ coordinator_forecast_hourly.update_method = async_setup_update_forecast_hourly(
+ RETRY_INTERVAL, RETRY_STOP
+ )
+
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
From c0cd76b3bfdf066e43a41b476a5381cd091ff5c7 Mon Sep 17 00:00:00 2001
From: Jan Bouwhuis
Date: Wed, 8 May 2024 21:42:11 +0200
Subject: [PATCH 13/19] Make the mqtt discovery update tasks eager and fix race
(#117105)
* Fix mqtt discovery race for update rapidly followed on creation
* Revert unrelated renaming local var
---
homeassistant/components/mqtt/mixins.py | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py
index 63df7c71c09fd5..68173da7297341 100644
--- a/homeassistant/components/mqtt/mixins.py
+++ b/homeassistant/components/mqtt/mixins.py
@@ -1015,8 +1015,7 @@ def discovery_callback(payload: MQTTDiscoveryPayload) -> None:
self.hass.async_create_task(
_async_process_discovery_update_and_remove(
payload, self._discovery_data
- ),
- eager_start=False,
+ )
)
elif self._discovery_update:
if old_payload != self._discovery_data[ATTR_DISCOVERY_PAYLOAD]:
@@ -1025,8 +1024,7 @@ def discovery_callback(payload: MQTTDiscoveryPayload) -> None:
self.hass.async_create_task(
_async_process_discovery_update(
payload, self._discovery_update, self._discovery_data
- ),
- eager_start=False,
+ )
)
else:
# Non-empty, unchanged payload: Ignore to avoid changing states
@@ -1059,6 +1057,15 @@ async def async_removed_from_registry(self) -> None:
# rediscovered after a restart
await async_remove_discovery_payload(self.hass, self._discovery_data)
+ @final
+ async def add_to_platform_finish(self) -> None:
+ """Finish adding entity to platform."""
+ await super().add_to_platform_finish()
+ # Only send the discovery done after the entity is fully added
+ # and the state is written to the state machine.
+ if self._discovery_data is not None:
+ send_discovery_done(self.hass, self._discovery_data)
+
@callback
def add_to_platform_abort(self) -> None:
"""Abort adding an entity to a platform."""
@@ -1218,8 +1225,6 @@ async def async_added_to_hass(self) -> None:
self._prepare_subscribe_topics()
await self._subscribe_topics()
await self.mqtt_async_added_to_hass()
- if self._discovery_data is not None:
- send_discovery_done(self.hass, self._discovery_data)
async def mqtt_async_added_to_hass(self) -> None:
"""Call before the discovery message is acknowledged.
From 09490d9e0a6901ca2af822f589038a198d6ee21e Mon Sep 17 00:00:00 2001
From: mletenay
Date: Thu, 9 May 2024 00:17:20 +0200
Subject: [PATCH 14/19] Bump goodwe to 0.3.5 (#117115)
---
homeassistant/components/goodwe/manifest.json | 2 +-
requirements_all.txt | 2 +-
requirements_test_all.txt | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/homeassistant/components/goodwe/manifest.json b/homeassistant/components/goodwe/manifest.json
index 59c259524c8f82..8506d1fd6afcc5 100644
--- a/homeassistant/components/goodwe/manifest.json
+++ b/homeassistant/components/goodwe/manifest.json
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/goodwe",
"iot_class": "local_polling",
"loggers": ["goodwe"],
- "requirements": ["goodwe==0.3.4"]
+ "requirements": ["goodwe==0.3.5"]
}
diff --git a/requirements_all.txt b/requirements_all.txt
index e39f08d66bf49b..1ee861f25bceb9 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -952,7 +952,7 @@ glances-api==0.6.0
goalzero==0.2.2
# homeassistant.components.goodwe
-goodwe==0.3.4
+goodwe==0.3.5
# homeassistant.components.google_mail
# homeassistant.components.google_tasks
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 2939c4e843ecff..189322bd545533 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -781,7 +781,7 @@ glances-api==0.6.0
goalzero==0.2.2
# homeassistant.components.goodwe
-goodwe==0.3.4
+goodwe==0.3.5
# homeassistant.components.google_mail
# homeassistant.components.google_tasks
From 1b519a4610ed2b74420a46c85b973a5fe971b538 Mon Sep 17 00:00:00 2001
From: "J. Nick Koston"
Date: Fri, 10 May 2024 00:47:13 -0500
Subject: [PATCH 15/19] Handle tilt position being None in HKC (#117141)
---
.../components/homekit_controller/cover.py | 4 ++-
.../homekit_controller/test_cover.py | 34 +++++++++++++++++++
2 files changed, 37 insertions(+), 1 deletion(-)
diff --git a/homeassistant/components/homekit_controller/cover.py b/homeassistant/components/homekit_controller/cover.py
index ca041d49e1151c..d0944db38f8466 100644
--- a/homeassistant/components/homekit_controller/cover.py
+++ b/homeassistant/components/homekit_controller/cover.py
@@ -212,13 +212,15 @@ def is_vertical_tilt(self) -> bool:
)
@property
- def current_cover_tilt_position(self) -> int:
+ def current_cover_tilt_position(self) -> int | None:
"""Return current position of cover tilt."""
tilt_position = self.service.value(CharacteristicsTypes.VERTICAL_TILT_CURRENT)
if not tilt_position:
tilt_position = self.service.value(
CharacteristicsTypes.HORIZONTAL_TILT_CURRENT
)
+ if tilt_position is None:
+ return None
# Recalculate to convert from arcdegree scale to percentage scale.
if self.is_vertical_tilt:
scale = 0.9
diff --git a/tests/components/homekit_controller/test_cover.py b/tests/components/homekit_controller/test_cover.py
index 671e9779d30008..2157eb5121220e 100644
--- a/tests/components/homekit_controller/test_cover.py
+++ b/tests/components/homekit_controller/test_cover.py
@@ -3,6 +3,7 @@
from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.services import ServicesTypes
+from homeassistant.const import STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
@@ -94,6 +95,24 @@ def create_window_covering_service_with_v_tilt_2(accessory):
tilt_target.maxValue = 0
+def create_window_covering_service_with_none_tilt(accessory):
+ """Define a window-covering characteristics as per page 219 of HAP spec.
+
+ This accessory uses None for the tilt value unexpectedly.
+ """
+ service = create_window_covering_service(accessory)
+
+ tilt_current = service.add_char(CharacteristicsTypes.VERTICAL_TILT_CURRENT)
+ tilt_current.value = None
+ tilt_current.minValue = -90
+ tilt_current.maxValue = 0
+
+ tilt_target = service.add_char(CharacteristicsTypes.VERTICAL_TILT_TARGET)
+ tilt_target.value = None
+ tilt_target.minValue = -90
+ tilt_target.maxValue = 0
+
+
async def test_change_window_cover_state(hass: HomeAssistant) -> None:
"""Test that we can turn a HomeKit alarm on and off again."""
helper = await setup_test_component(hass, create_window_covering_service)
@@ -212,6 +231,21 @@ async def test_read_window_cover_tilt_vertical_2(hass: HomeAssistant) -> None:
assert state.attributes["current_tilt_position"] == 83
+async def test_read_window_cover_tilt_missing_tilt(hass: HomeAssistant) -> None:
+ """Test that missing tilt is handled."""
+ helper = await setup_test_component(
+ hass, create_window_covering_service_with_none_tilt
+ )
+
+ await helper.async_update(
+ ServicesTypes.WINDOW_COVERING,
+ {CharacteristicsTypes.OBSTRUCTION_DETECTED: True},
+ )
+ state = await helper.poll_and_get_state()
+ assert "current_tilt_position" not in state.attributes
+ assert state.state != STATE_UNAVAILABLE
+
+
async def test_write_window_cover_tilt_horizontal(hass: HomeAssistant) -> None:
"""Test that horizontal tilt is written correctly."""
helper = await setup_test_component(
From 56b38cd8427a2c131d61eddf35f559f9e7942d4c Mon Sep 17 00:00:00 2001
From: Jan Bouwhuis
Date: Thu, 9 May 2024 16:31:36 +0200
Subject: [PATCH 16/19] Fix typo in xiaomi_ble translation strings (#117144)
---
homeassistant/components/xiaomi_ble/strings.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/homeassistant/components/xiaomi_ble/strings.json b/homeassistant/components/xiaomi_ble/strings.json
index 8ee8bac3feaef8..048c9bd92e2485 100644
--- a/homeassistant/components/xiaomi_ble/strings.json
+++ b/homeassistant/components/xiaomi_ble/strings.json
@@ -83,7 +83,7 @@
"button_fan": "Button Fan \"{subtype}\"",
"button_swing": "Button Swing \"{subtype}\"",
"button_decrease_speed": "Button Decrease Speed \"{subtype}\"",
- "button_increase_speed": "Button Inrease Speed \"{subtype}\"",
+ "button_increase_speed": "Button Increase Speed \"{subtype}\"",
"button_stop": "Button Stop \"{subtype}\"",
"button_light": "Button Light \"{subtype}\"",
"button_wind_speed": "Button Wind Speed \"{subtype}\"",
From f07c00a05b24f1808a7e30d1361fff2f11d167cd Mon Sep 17 00:00:00 2001
From: Diogo Gomes
Date: Fri, 10 May 2024 18:59:28 +0100
Subject: [PATCH 17/19] Bump pytrydan to 0.6.0 (#117162)
---
homeassistant/components/v2c/manifest.json | 2 +-
requirements_all.txt | 2 +-
requirements_test_all.txt | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/homeassistant/components/v2c/manifest.json b/homeassistant/components/v2c/manifest.json
index ce0e9d7b8470c8..fb234d726e867e 100644
--- a/homeassistant/components/v2c/manifest.json
+++ b/homeassistant/components/v2c/manifest.json
@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/v2c",
"iot_class": "local_polling",
- "requirements": ["pytrydan==0.4.0"]
+ "requirements": ["pytrydan==0.6.0"]
}
diff --git a/requirements_all.txt b/requirements_all.txt
index 1ee861f25bceb9..e0cc726f3e0d05 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -2337,7 +2337,7 @@ pytradfri[async]==9.0.1
pytrafikverket==0.3.10
# homeassistant.components.v2c
-pytrydan==0.4.0
+pytrydan==0.6.0
# homeassistant.components.usb
pyudev==0.24.1
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 189322bd545533..6b9ce0504f511e 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -1816,7 +1816,7 @@ pytradfri[async]==9.0.1
pytrafikverket==0.3.10
# homeassistant.components.v2c
-pytrydan==0.4.0
+pytrydan==0.6.0
# homeassistant.components.usb
pyudev==0.24.1
From 2c8b3ac8bbf76f764214b760db82990e32c915ed Mon Sep 17 00:00:00 2001
From: Robert Resch
Date: Fri, 10 May 2024 13:33:18 +0200
Subject: [PATCH 18/19] Bump deebot-client to 7.2.0 (#117189)
---
homeassistant/components/ecovacs/manifest.json | 2 +-
requirements_all.txt | 2 +-
requirements_test_all.txt | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/homeassistant/components/ecovacs/manifest.json b/homeassistant/components/ecovacs/manifest.json
index aad04d9ec87907..e6bd59e3d12c94 100644
--- a/homeassistant/components/ecovacs/manifest.json
+++ b/homeassistant/components/ecovacs/manifest.json
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
"iot_class": "cloud_push",
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
- "requirements": ["py-sucks==0.9.9", "deebot-client==7.1.0"]
+ "requirements": ["py-sucks==0.9.9", "deebot-client==7.2.0"]
}
diff --git a/requirements_all.txt b/requirements_all.txt
index e0cc726f3e0d05..f0acc214f78400 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -697,7 +697,7 @@ debugpy==1.8.1
# decora==0.6
# homeassistant.components.ecovacs
-deebot-client==7.1.0
+deebot-client==7.2.0
# homeassistant.components.ihc
# homeassistant.components.namecheapdns
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 6b9ce0504f511e..47f4f1baf51445 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -575,7 +575,7 @@ dbus-fast==2.21.1
debugpy==1.8.1
# homeassistant.components.ecovacs
-deebot-client==7.1.0
+deebot-client==7.2.0
# homeassistant.components.ihc
# homeassistant.components.namecheapdns
From e2da28fbdb3c226f624dce7c18eedc62abcd8c87 Mon Sep 17 00:00:00 2001
From: Paulus Schoutsen
Date: Fri, 10 May 2024 18:14:24 +0000
Subject: [PATCH 19/19] Bump version to 2024.5.3
---
homeassistant/const.py | 2 +-
pyproject.toml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/homeassistant/const.py b/homeassistant/const.py
index e9e1231712ec8d..4bab6d0f12708c 100644
--- a/homeassistant/const.py
+++ b/homeassistant/const.py
@@ -23,7 +23,7 @@
APPLICATION_NAME: Final = "HomeAssistant"
MAJOR_VERSION: Final = 2024
MINOR_VERSION: Final = 5
-PATCH_VERSION: Final = "2"
+PATCH_VERSION: Final = "3"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)
diff --git a/pyproject.toml b/pyproject.toml
index 8fb7839c628196..5c24c020e82d9d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "homeassistant"
-version = "2024.5.2"
+version = "2024.5.3"
license = {text = "Apache-2.0"}
description = "Open-source home automation platform running on Python 3."
readme = "README.rst"