Skip to content

Commit

Permalink
2024.4.1 (#114934)
Browse files Browse the repository at this point in the history
  • Loading branch information
frenck committed Apr 5, 2024
2 parents b613976 + 9560613 commit b1fb77c
Show file tree
Hide file tree
Showing 38 changed files with 433 additions and 112 deletions.
2 changes: 1 addition & 1 deletion homeassistant/components/airzone_cloud/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/airzone_cloud",
"iot_class": "cloud_push",
"loggers": ["aioairzone_cloud"],
"requirements": ["aioairzone-cloud==0.4.6"]
"requirements": ["aioairzone-cloud==0.4.7"]
}
21 changes: 20 additions & 1 deletion homeassistant/components/august/activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from datetime import datetime
from functools import partial
import logging
from time import monotonic

from aiohttp import ClientError
from yalexs.activity import Activity, ActivityType
Expand All @@ -26,9 +27,11 @@
ACTIVITY_STREAM_FETCH_LIMIT = 10
ACTIVITY_CATCH_UP_FETCH_LIMIT = 2500

INITIAL_LOCK_RESYNC_TIME = 60

# If there is a storm of activity (ie lock, unlock, door open, door close, etc)
# we want to debounce the updates so we don't hammer the activity api too much.
ACTIVITY_DEBOUNCE_COOLDOWN = 3
ACTIVITY_DEBOUNCE_COOLDOWN = 4


@callback
Expand Down Expand Up @@ -62,6 +65,7 @@ def __init__(
self.pubnub = pubnub
self._update_debounce: dict[str, Debouncer] = {}
self._update_debounce_jobs: dict[str, HassJob] = {}
self._start_time: float | None = None

@callback
def _async_update_house_id_later(self, debouncer: Debouncer, _: datetime) -> None:
Expand All @@ -70,6 +74,7 @@ def _async_update_house_id_later(self, debouncer: Debouncer, _: datetime) -> Non

async def async_setup(self) -> None:
"""Token refresh check and catch up the activity stream."""
self._start_time = monotonic()
update_debounce = self._update_debounce
update_debounce_jobs = self._update_debounce_jobs
for house_id in self._house_ids:
Expand Down Expand Up @@ -140,11 +145,25 @@ def async_schedule_house_id_refresh(self, house_id: str) -> None:

debouncer = self._update_debounce[house_id]
debouncer.async_schedule_call()

# Schedule two updates past the debounce time
# to ensure we catch the case where the activity
# api does not update right away and we need to poll
# it again. Sometimes the lock operator or a doorbell
# will not show up in the activity stream right away.
# Only do additional polls if we are past
# the initial lock resync time to avoid a storm
# of activity at setup.
if (
not self._start_time
or monotonic() - self._start_time < INITIAL_LOCK_RESYNC_TIME
):
_LOGGER.debug(
"Skipping additional updates due to ongoing initial lock resync time"
)
return

_LOGGER.debug("Scheduling additional updates for house id %s", house_id)
job = self._update_debounce_jobs[house_id]
for step in (1, 2):
future_updates.append(
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/august/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
# Limit battery, online, and hardware updates to hourly
# in order to reduce the number of api requests and
# avoid hitting rate limits
MIN_TIME_BETWEEN_DETAIL_UPDATES = timedelta(hours=1)
MIN_TIME_BETWEEN_DETAIL_UPDATES = timedelta(hours=24)

# Activity needs to be checked more frequently as the
# doorbell motion and rings are included here
Expand Down
33 changes: 15 additions & 18 deletions homeassistant/components/august/subscriber.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,27 +49,30 @@ def _async_scheduled_refresh(self, now: datetime) -> None:
"""Call the refresh method."""
self._hass.async_create_task(self._async_refresh(now), eager_start=True)

@callback
def _async_cancel_update_interval(self, _: Event | None = None) -> None:
"""Cancel the scheduled update."""
if self._unsub_interval:
self._unsub_interval()
self._unsub_interval = None

@callback
def _async_setup_listeners(self) -> None:
"""Create interval and stop listeners."""
self._async_cancel_update_interval()
self._unsub_interval = async_track_time_interval(
self._hass,
self._async_scheduled_refresh,
self._update_interval,
name="august refresh",
)

@callback
def _async_cancel_update_interval(_: Event) -> None:
self._stop_interval = None
if self._unsub_interval:
self._unsub_interval()

self._stop_interval = self._hass.bus.async_listen(
EVENT_HOMEASSISTANT_STOP,
_async_cancel_update_interval,
run_immediately=True,
)
if not self._stop_interval:
self._stop_interval = self._hass.bus.async_listen(
EVENT_HOMEASSISTANT_STOP,
self._async_cancel_update_interval,
run_immediately=True,
)

@callback
def async_unsubscribe_device_id(
Expand All @@ -82,13 +85,7 @@ def async_unsubscribe_device_id(

if self._subscriptions:
return

if self._unsub_interval:
self._unsub_interval()
self._unsub_interval = None
if self._stop_interval:
self._stop_interval()
self._stop_interval = None
self._async_cancel_update_interval()

@callback
def async_signal_device_id_update(self, device_id: str) -> None:
Expand Down
16 changes: 8 additions & 8 deletions homeassistant/components/axis/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def __init__(self, hub: AxisHub) -> None:
mjpeg_url=self.mjpeg_source,
still_image_url=self.image_source,
authentication=HTTP_DIGEST_AUTHENTICATION,
verify_ssl=False,
unique_id=f"{hub.unique_id}-camera",
)

Expand All @@ -74,16 +75,18 @@ def _generate_sources(self) -> None:
Additionally used when device change IP address.
"""
proto = self.hub.config.protocol
host = self.hub.config.host
port = self.hub.config.port

image_options = self.generate_options(skip_stream_profile=True)
self._still_image_url = (
f"http://{self.hub.config.host}:{self.hub.config.port}/axis-cgi"
f"/jpg/image.cgi{image_options}"
f"{proto}://{host}:{port}/axis-cgi/jpg/image.cgi{image_options}"
)

mjpeg_options = self.generate_options()
self._mjpeg_url = (
f"http://{self.hub.config.host}:{self.hub.config.port}/axis-cgi"
f"/mjpg/video.cgi{mjpeg_options}"
f"{proto}://{host}:{port}/axis-cgi/mjpg/video.cgi{mjpeg_options}"
)

stream_options = self.generate_options(add_video_codec_h264=True)
Expand All @@ -95,10 +98,7 @@ def _generate_sources(self) -> None:
self.hub.additional_diagnostics["camera_sources"] = {
"Image": self._still_image_url,
"MJPEG": self._mjpeg_url,
"Stream": (
f"rtsp://user:pass@{self.hub.config.host}/axis-media"
f"/media.amp{stream_options}"
),
"Stream": (f"rtsp://user:pass@{host}/axis-media/media.amp{stream_options}"),
}

@property
Expand Down
11 changes: 4 additions & 7 deletions homeassistant/components/axis/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,16 +168,13 @@ async def _redo_configuration(
self, entry_data: Mapping[str, Any], keep_password: bool
) -> ConfigFlowResult:
"""Re-run configuration step."""
protocol = entry_data.get(CONF_PROTOCOL, "http")
password = entry_data[CONF_PASSWORD] if keep_password else ""
self.discovery_schema = {
vol.Required(
CONF_PROTOCOL, default=entry_data.get(CONF_PROTOCOL, "http")
): str,
vol.Required(CONF_PROTOCOL, default=protocol): vol.In(PROTOCOL_CHOICES),
vol.Required(CONF_HOST, default=entry_data[CONF_HOST]): str,
vol.Required(CONF_USERNAME, default=entry_data[CONF_USERNAME]): str,
vol.Required(
CONF_PASSWORD,
default=entry_data[CONF_PASSWORD] if keep_password else "",
): str,
vol.Required(CONF_PASSWORD, default=password): str,
vol.Required(CONF_PORT, default=entry_data[CONF_PORT]): int,
}

Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/axis/hub/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
CONF_NAME,
CONF_PASSWORD,
CONF_PORT,
CONF_PROTOCOL,
CONF_TRIGGER_TIME,
CONF_USERNAME,
)
Expand All @@ -31,6 +32,7 @@ class AxisConfig:

entry: ConfigEntry

protocol: str
host: str
port: int
username: str
Expand All @@ -54,6 +56,7 @@ def from_config_entry(cls, config_entry: ConfigEntry) -> Self:
options = config_entry.options
return cls(
entry=config_entry,
protocol=config.get(CONF_PROTOCOL, "http"),
host=config[CONF_HOST],
username=config[CONF_USERNAME],
password=config[CONF_PASSWORD],
Expand Down
65 changes: 44 additions & 21 deletions homeassistant/components/downloader/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
import voluptuous as vol

from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.core import (
DOMAIN as HOMEASSISTANT_DOMAIN,
HomeAssistant,
ServiceCall,
)
from homeassistant.data_entry_flow import FlowResultType
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
Expand Down Expand Up @@ -43,6 +47,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
if DOMAIN not in config:
return True

hass.async_create_task(_async_import_config(hass, config))
return True


async def _async_import_config(hass: HomeAssistant, config: ConfigType) -> None:
"""Import the Downloader component from the YAML file."""

import_result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
Expand All @@ -51,28 +62,40 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
},
)

translation_key = "deprecated_yaml"
if (
import_result["type"] == FlowResultType.ABORT
and import_result["reason"] == "import_failed"
and import_result["reason"] != "single_instance_allowed"
):
translation_key = "import_failed"

async_create_issue(
hass,
DOMAIN,
f"deprecated_yaml_{DOMAIN}",
breaks_in_ha_version="2024.9.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=IssueSeverity.WARNING,
translation_key=translation_key,
translation_placeholders={
"domain": DOMAIN,
"integration_title": "Downloader",
},
)
return True
async_create_issue(
hass,
DOMAIN,
f"deprecated_yaml_{DOMAIN}",
breaks_in_ha_version="2024.10.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=IssueSeverity.WARNING,
translation_key="directory_does_not_exist",
translation_placeholders={
"domain": DOMAIN,
"integration_title": "Downloader",
"url": "/config/integrations/dashboard/add?domain=downloader",
},
)
else:
async_create_issue(
hass,
HOMEASSISTANT_DOMAIN,
f"deprecated_yaml_{DOMAIN}",
breaks_in_ha_version="2024.10.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=IssueSeverity.WARNING,
translation_key="deprecated_yaml",
translation_placeholders={
"domain": DOMAIN,
"integration_title": "Downloader",
},
)


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
Expand All @@ -83,7 +106,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if not os.path.isabs(download_path):
download_path = hass.config.path(download_path)

if not os.path.isdir(download_path):
if not await hass.async_add_executor_job(os.path.isdir, download_path):
_LOGGER.error(
"Download path %s does not exist. File Downloader not active", download_path
)
Expand Down
12 changes: 8 additions & 4 deletions homeassistant/components/downloader/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,16 @@ async def async_step_user(
errors=errors,
)

async def async_step_import(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
async def async_step_import(self, user_input: dict[str, Any]) -> ConfigFlowResult:
"""Handle a flow initiated by configuration file."""
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")

return await self.async_step_user(user_input)
try:
await self._validate_input(user_input)
except DirectoryDoesNotExist:
return self.async_abort(reason="directory_does_not_exist")
return self.async_create_entry(title=DEFAULT_NAME, data=user_input)

async def _validate_input(self, user_input: dict[str, Any]) -> None:
"""Validate the user input if the directory exists."""
Expand Down
8 changes: 2 additions & 6 deletions homeassistant/components/downloader/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,9 @@
}
},
"issues": {
"deprecated_yaml": {
"title": "The {integration_title} YAML configuration is being removed",
"description": "Configuring {integration_title} using YAML is being removed.\n\nYour configuration is already imported.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue."
},
"import_failed": {
"directory_does_not_exist": {
"title": "The {integration_title} failed to import",
"description": "The {integration_title} integration failed to import.\n\nPlease check the logs for more details."
"description": "The {integration_title} integration failed to import because the configured directory does not exist.\n\nEnsure the directory exists and restart Home Assistant to try again or remove the {integration_title} configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually."
}
}
}
2 changes: 1 addition & 1 deletion homeassistant/components/frontend/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20240403.1"]
"requirements": ["home-assistant-frontend==20240404.1"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/lovelace/cast.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ async def _get_dashboard_info(hass, url_path):
"views": views,
}

if config is None:
if config is None or "views" not in config:
return data

for idx, view in enumerate(config["views"]):
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/lutron/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def turn_on(self, **kwargs: Any) -> None:
else:
brightness = self._prev_brightness
self._prev_brightness = brightness
args = {"new_level": brightness}
args = {"new_level": to_lutron_level(brightness)}
if ATTR_TRANSITION in kwargs:
args["fade_time_seconds"] = kwargs[ATTR_TRANSITION]
self._lutron_device.set_level(**args)
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/myuplink/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from http import HTTPStatus

from aiohttp import ClientError, ClientResponseError
from myuplink import MyUplinkAPI, get_manufacturer, get_system_name
from myuplink import MyUplinkAPI, get_manufacturer, get_model, get_system_name

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
Expand Down Expand Up @@ -92,7 +92,7 @@ def create_devices(
identifiers={(DOMAIN, device_id)},
name=get_system_name(system),
manufacturer=get_manufacturer(device),
model=device.productName,
model=get_model(device),
sw_version=device.firmwareCurrent,
serial_number=device.product_serial_number,
)

0 comments on commit b1fb77c

Please sign in to comment.