Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2024.5.3 #117203

Merged
merged 19 commits into from
May 10, 2024
Merged

2024.5.3 #117203

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 6 additions & 6 deletions homeassistant/components/bring/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,19 @@
"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`"
}
}
}
},
"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"
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/ecovacs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/enphase_envoy/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/goodwe/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/goodwe/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -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") == "%",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 3 additions & 1 deletion homeassistant/components/homekit_controller/cover.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 25 additions & 16 deletions homeassistant/components/mqtt/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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."""
Expand Down Expand Up @@ -497,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(
Expand Down Expand Up @@ -883,7 +881,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,
Expand Down Expand Up @@ -993,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 "",
Expand Down
17 changes: 11 additions & 6 deletions homeassistant/components/mqtt/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand All @@ -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
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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.
Expand Down
70 changes: 48 additions & 22 deletions homeassistant/components/nws/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/roku/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 9 additions & 3 deletions homeassistant/components/sonos/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/sonos/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,5 +173,10 @@
}
}
}
},
"exceptions": {
"invalid_favorite": {
"message": "Could not find a Sonos favorite: {name}"
}
}
}