Skip to content

Commit

Permalink
2024.5.2 (#116937)
Browse files Browse the repository at this point in the history
  • Loading branch information
frenck committed May 6, 2024
2 parents ab8a811 + eb6ccea commit a8f3b69
Show file tree
Hide file tree
Showing 33 changed files with 303 additions and 136 deletions.
8 changes: 7 additions & 1 deletion homeassistant/components/airthings_ble/__init__.py
Expand Up @@ -16,7 +16,7 @@
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util.unit_system import METRIC_SYSTEM

from .const import DEFAULT_SCAN_INTERVAL, DOMAIN
from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, MAX_RETRIES_AFTER_STARTUP

PLATFORMS: list[Platform] = [Platform.SENSOR]

Expand Down Expand Up @@ -61,6 +61,12 @@ async def _async_update_method() -> AirthingsDevice:

await coordinator.async_config_entry_first_refresh()

# Once its setup and we know we are not going to delay
# the startup of Home Assistant, we can set the max attempts
# to a higher value. If the first connection attempt fails,
# Home Assistant's built-in retry logic will take over.
airthings.set_max_attempts(MAX_RETRIES_AFTER_STARTUP)

hass.data[DOMAIN][entry.entry_id] = coordinator

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/airthings_ble/const.py
Expand Up @@ -7,3 +7,5 @@
VOLUME_PICOCURIE = "pCi/L"

DEFAULT_SCAN_INTERVAL = 300

MAX_RETRIES_AFTER_STARTUP = 5
2 changes: 1 addition & 1 deletion homeassistant/components/airthings_ble/manifest.json
Expand Up @@ -24,5 +24,5 @@
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/airthings_ble",
"iot_class": "local_polling",
"requirements": ["airthings-ble==0.8.0"]
"requirements": ["airthings-ble==0.9.0"]
}
93 changes: 71 additions & 22 deletions homeassistant/components/amcrest/__init__.py
Expand Up @@ -35,7 +35,7 @@
HTTP_BASIC_AUTHENTICATION,
Platform,
)
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import Unauthorized, UnknownUser
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
Expand Down Expand Up @@ -177,7 +177,8 @@ def async_available_flag(self) -> asyncio.Event:
"""Return event flag that indicates if camera's API is responding."""
return self._async_wrap_event_flag

def _start_recovery(self) -> None:
@callback
def _async_start_recovery(self) -> None:
self.available_flag.clear()
self.async_available_flag.clear()
async_dispatcher_send(
Expand Down Expand Up @@ -222,50 +223,98 @@ async def _async_command_wrapper(self) -> AsyncIterator[None]:
yield
except LoginError as ex:
async with self._async_wrap_lock:
self._handle_offline(ex)
self._async_handle_offline(ex)
raise
except AmcrestError:
async with self._async_wrap_lock:
self._handle_error()
self._async_handle_error()
raise
async with self._async_wrap_lock:
self._set_online()
self._async_set_online()

def _handle_offline(self, ex: Exception) -> None:
def _handle_offline_thread_safe(self, ex: Exception) -> bool:
"""Handle camera offline status shared between threads and event loop.
Returns if the camera was online as a bool.
"""
with self._wrap_lock:
was_online = self.available
was_login_err = self._wrap_login_err
self._wrap_login_err = True
if not was_login_err:
_LOGGER.error("%s camera offline: Login error: %s", self._wrap_name, ex)
if was_online:
self._start_recovery()
return was_online

def _handle_error(self) -> None:
def _handle_offline(self, ex: Exception) -> None:
"""Handle camera offline status from a thread."""
if self._handle_offline_thread_safe(ex):
self._hass.loop.call_soon_threadsafe(self._async_start_recovery)

@callback
def _async_handle_offline(self, ex: Exception) -> None:
if self._handle_offline_thread_safe(ex):
self._async_start_recovery()

def _handle_error_thread_safe(self) -> bool:
"""Handle camera error status shared between threads and event loop.
Returns if the camera was online and is now offline as
a bool.
"""
with self._wrap_lock:
was_online = self.available
errs = self._wrap_errors = self._wrap_errors + 1
offline = not self.available
_LOGGER.debug("%s camera errs: %i", self._wrap_name, errs)
if was_online and offline:
return was_online and offline

def _handle_error(self) -> None:
"""Handle camera error status from a thread."""
if self._handle_error_thread_safe():
_LOGGER.error("%s camera offline: Too many errors", self._wrap_name)
self._start_recovery()
self._hass.loop.call_soon_threadsafe(self._async_start_recovery)

def _set_online(self) -> None:
@callback
def _async_handle_error(self) -> None:
"""Handle camera error status from the event loop."""
if self._handle_error_thread_safe():
_LOGGER.error("%s camera offline: Too many errors", self._wrap_name)
self._async_start_recovery()

def _set_online_thread_safe(self) -> bool:
"""Set camera online status shared between threads and event loop.
Returns if the camera was offline as a bool.
"""
with self._wrap_lock:
was_offline = not self.available
self._wrap_errors = 0
self._wrap_login_err = False
if was_offline:
assert self._unsub_recheck is not None
self._unsub_recheck()
self._unsub_recheck = None
_LOGGER.error("%s camera back online", self._wrap_name)
self.available_flag.set()
self.async_available_flag.set()
async_dispatcher_send(
self._hass, service_signal(SERVICE_UPDATE, self._wrap_name)
)
return was_offline

def _set_online(self) -> None:
"""Set camera online status from a thread."""
if self._set_online_thread_safe():
self._hass.loop.call_soon_threadsafe(self._async_signal_online)

@callback
def _async_set_online(self) -> None:
"""Set camera online status from the event loop."""
if self._set_online_thread_safe():
self._async_signal_online()

@callback
def _async_signal_online(self) -> None:
"""Signal that camera is back online."""
assert self._unsub_recheck is not None
self._unsub_recheck()
self._unsub_recheck = None
_LOGGER.error("%s camera back online", self._wrap_name)
self.available_flag.set()
self.async_available_flag.set()
async_dispatcher_send(
self._hass, service_signal(SERVICE_UPDATE, self._wrap_name)
)

async def _wrap_test_online(self, now: datetime) -> None:
"""Test if camera is back online."""
Expand Down
5 changes: 3 additions & 2 deletions homeassistant/components/amcrest/camera.py
Expand Up @@ -16,7 +16,7 @@
from homeassistant.components.camera import Camera, CameraEntityFeature
from homeassistant.components.ffmpeg import FFmpegManager, get_ffmpeg_manager
from homeassistant.const import ATTR_ENTITY_ID, CONF_NAME, STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import (
async_aiohttp_proxy_stream,
Expand Down Expand Up @@ -325,7 +325,8 @@ def is_on(self) -> bool:

# Other Entity method overrides

async def async_on_demand_update(self) -> None:
@callback
def async_on_demand_update(self) -> None:
"""Update state."""
self.async_schedule_update_ha_state(True)

Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/androidtv_remote/manifest.json
Expand Up @@ -8,6 +8,6 @@
"iot_class": "local_push",
"loggers": ["androidtvremote2"],
"quality_scale": "platinum",
"requirements": ["androidtvremote2==0.0.14"],
"requirements": ["androidtvremote2==0.0.15"],
"zeroconf": ["_androidtvremote2._tcp.local."]
}
2 changes: 1 addition & 1 deletion homeassistant/components/bluetooth/manifest.json
Expand Up @@ -16,7 +16,7 @@
"requirements": [
"bleak==0.21.1",
"bleak-retry-connector==3.5.0",
"bluetooth-adapters==0.19.1",
"bluetooth-adapters==0.19.2",
"bluetooth-auto-recovery==1.4.2",
"bluetooth-data-tools==1.19.0",
"dbus-fast==2.21.1",
Expand Down
6 changes: 3 additions & 3 deletions homeassistant/components/bosch_shc/switch.py
Expand Up @@ -43,21 +43,21 @@ class SHCSwitchEntityDescription(SwitchEntityDescription):
"smartplug": SHCSwitchEntityDescription(
key="smartplug",
device_class=SwitchDeviceClass.OUTLET,
on_key="state",
on_key="switchstate",
on_value=SHCSmartPlug.PowerSwitchService.State.ON,
should_poll=False,
),
"smartplugcompact": SHCSwitchEntityDescription(
key="smartplugcompact",
device_class=SwitchDeviceClass.OUTLET,
on_key="state",
on_key="switchstate",
on_value=SHCSmartPlugCompact.PowerSwitchService.State.ON,
should_poll=False,
),
"lightswitch": SHCSwitchEntityDescription(
key="lightswitch",
device_class=SwitchDeviceClass.SWITCH,
on_key="state",
on_key="switchstate",
on_value=SHCLightSwitch.PowerSwitchService.State.ON,
should_poll=False,
),
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/conversation/http.py
Expand Up @@ -142,6 +142,9 @@ async def websocket_list_agents(
agent = manager.async_get_agent(agent_info.id)
assert agent is not None

if isinstance(agent, ConversationEntity):
continue

supported_languages = agent.supported_languages
if language and supported_languages != MATCH_ALL:
supported_languages = language_util.matches(
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/frontend/manifest.json
Expand Up @@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20240501.0"]
"requirements": ["home-assistant-frontend==20240501.1"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/goodwe/manifest.json
Expand Up @@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/goodwe",
"iot_class": "local_polling",
"loggers": ["goodwe"],
"requirements": ["goodwe==0.3.2"]
"requirements": ["goodwe==0.3.4"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/imap/config_flow.py
Expand Up @@ -75,7 +75,7 @@
vol.Optional(CONF_FOLDER, default="INBOX"): str,
vol.Optional(CONF_SEARCH, default="UnSeen UnDeleted"): str,
# The default for new entries is to not include text and headers
vol.Optional(CONF_EVENT_MESSAGE_DATA, default=[]): cv.ensure_list,
vol.Optional(CONF_EVENT_MESSAGE_DATA, default=[]): EVENT_MESSAGE_DATA_SELECTOR,
}
)
CONFIG_SCHEMA_ADVANCED = {
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/lutron/event.py
Expand Up @@ -106,4 +106,4 @@ def handle_event(
}
self.hass.bus.fire("lutron_event", data)
self._trigger_event(action)
self.async_write_ha_state()
self.schedule_update_ha_state()
4 changes: 3 additions & 1 deletion homeassistant/components/mqtt/client.py
Expand Up @@ -84,7 +84,7 @@
_LOGGER = logging.getLogger(__name__)

DISCOVERY_COOLDOWN = 5
INITIAL_SUBSCRIBE_COOLDOWN = 1.0
INITIAL_SUBSCRIBE_COOLDOWN = 3.0
SUBSCRIBE_COOLDOWN = 0.1
UNSUBSCRIBE_COOLDOWN = 0.1
TIMEOUT_ACK = 10
Expand Down Expand Up @@ -891,6 +891,7 @@ async def _async_resubscribe_and_publish_birth_message(
qos=birth_message.qos,
retain=birth_message.retain,
)
_LOGGER.info("MQTT client initialized, birth message sent")

@callback
def _async_mqtt_on_connect(
Expand Down Expand Up @@ -950,6 +951,7 @@ def _async_mqtt_on_connect(
name="mqtt re-subscribe",
)
self._subscribe_debouncer.set_timeout(SUBSCRIBE_COOLDOWN)
_LOGGER.info("MQTT client initialized")

self._async_connection_result(True)

Expand Down
9 changes: 0 additions & 9 deletions homeassistant/components/opower/sensor.py
Expand Up @@ -69,7 +69,6 @@ class OpowerEntityDescription(SensorEntityDescription):
name="Current bill electric cost to date",
device_class=SensorDeviceClass.MONETARY,
native_unit_of_measurement="USD",
suggested_unit_of_measurement="USD",
state_class=SensorStateClass.TOTAL,
suggested_display_precision=0,
value_fn=lambda data: data.cost_to_date,
Expand All @@ -79,7 +78,6 @@ class OpowerEntityDescription(SensorEntityDescription):
name="Current bill electric forecasted cost",
device_class=SensorDeviceClass.MONETARY,
native_unit_of_measurement="USD",
suggested_unit_of_measurement="USD",
state_class=SensorStateClass.TOTAL,
suggested_display_precision=0,
value_fn=lambda data: data.forecasted_cost,
Expand All @@ -89,7 +87,6 @@ class OpowerEntityDescription(SensorEntityDescription):
name="Typical monthly electric cost",
device_class=SensorDeviceClass.MONETARY,
native_unit_of_measurement="USD",
suggested_unit_of_measurement="USD",
state_class=SensorStateClass.TOTAL,
suggested_display_precision=0,
value_fn=lambda data: data.typical_cost,
Expand All @@ -101,7 +98,6 @@ class OpowerEntityDescription(SensorEntityDescription):
name="Current bill gas usage to date",
device_class=SensorDeviceClass.GAS,
native_unit_of_measurement=UnitOfVolume.CENTUM_CUBIC_FEET,
suggested_unit_of_measurement=UnitOfVolume.CENTUM_CUBIC_FEET,
state_class=SensorStateClass.TOTAL,
suggested_display_precision=0,
value_fn=lambda data: data.usage_to_date,
Expand All @@ -111,7 +107,6 @@ class OpowerEntityDescription(SensorEntityDescription):
name="Current bill gas forecasted usage",
device_class=SensorDeviceClass.GAS,
native_unit_of_measurement=UnitOfVolume.CENTUM_CUBIC_FEET,
suggested_unit_of_measurement=UnitOfVolume.CENTUM_CUBIC_FEET,
state_class=SensorStateClass.TOTAL,
suggested_display_precision=0,
value_fn=lambda data: data.forecasted_usage,
Expand All @@ -121,7 +116,6 @@ class OpowerEntityDescription(SensorEntityDescription):
name="Typical monthly gas usage",
device_class=SensorDeviceClass.GAS,
native_unit_of_measurement=UnitOfVolume.CENTUM_CUBIC_FEET,
suggested_unit_of_measurement=UnitOfVolume.CENTUM_CUBIC_FEET,
state_class=SensorStateClass.TOTAL,
suggested_display_precision=0,
value_fn=lambda data: data.typical_usage,
Expand All @@ -131,7 +125,6 @@ class OpowerEntityDescription(SensorEntityDescription):
name="Current bill gas cost to date",
device_class=SensorDeviceClass.MONETARY,
native_unit_of_measurement="USD",
suggested_unit_of_measurement="USD",
state_class=SensorStateClass.TOTAL,
suggested_display_precision=0,
value_fn=lambda data: data.cost_to_date,
Expand All @@ -141,7 +134,6 @@ class OpowerEntityDescription(SensorEntityDescription):
name="Current bill gas forecasted cost",
device_class=SensorDeviceClass.MONETARY,
native_unit_of_measurement="USD",
suggested_unit_of_measurement="USD",
state_class=SensorStateClass.TOTAL,
suggested_display_precision=0,
value_fn=lambda data: data.forecasted_cost,
Expand All @@ -151,7 +143,6 @@ class OpowerEntityDescription(SensorEntityDescription):
name="Typical monthly gas cost",
device_class=SensorDeviceClass.MONETARY,
native_unit_of_measurement="USD",
suggested_unit_of_measurement="USD",
state_class=SensorStateClass.TOTAL,
suggested_display_precision=0,
value_fn=lambda data: data.typical_cost,
Expand Down
6 changes: 3 additions & 3 deletions homeassistant/components/radarr/coordinator.py
Expand Up @@ -46,7 +46,7 @@ class RadarrDataUpdateCoordinator(DataUpdateCoordinator[T], Generic[T], ABC):
"""Data update coordinator for the Radarr integration."""

config_entry: ConfigEntry
update_interval = timedelta(seconds=30)
_update_interval = timedelta(seconds=30)

def __init__(
self,
Expand All @@ -59,7 +59,7 @@ def __init__(
hass=hass,
logger=LOGGER,
name=DOMAIN,
update_interval=self.update_interval,
update_interval=self._update_interval,
)
self.api_client = api_client
self.host_configuration = host_configuration
Expand Down Expand Up @@ -133,7 +133,7 @@ async def _fetch_data(self) -> int:
class CalendarUpdateCoordinator(RadarrDataUpdateCoordinator[None]):
"""Calendar update coordinator."""

update_interval = timedelta(hours=1)
_update_interval = timedelta(hours=1)

def __init__(
self,
Expand Down

0 comments on commit a8f3b69

Please sign in to comment.