Skip to content
Merged
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
7af5bcc
Suppress the placeholder water temperature on Flo valves without the …
ajplotkin Sep 19, 2026
0527dcd
Reuse an existing Supervisor system user instead of creating a duplic…
agners Sep 19, 2026
b6fb2f7
alexa_devices: ensure consistent naming (#182673)
jamesonuk Sep 19, 2026
4a6b3a2
Bring every Vistapool module above 95% test coverage (#182482)
fdebrus Sep 19, 2026
cbc4351
Fix thumbnails of Kodi PVR channels (#181947)
ksooo Sep 19, 2026
2966e25
Use batch device status updates in Imou coordinator (#182471)
Imou-OpenPlatform Sep 19, 2026
d4d558a
Handle unrecognized Cync color modes without invalid fallback (#181081)
azuttre Sep 19, 2026
0063967
Migrate the UniFi Protect sense sensors to the public API (#182662)
RaHehl Sep 19, 2026
7ca2d9b
Add siren support to the UniFi Protect API key only mode (#182610)
RaHehl Sep 19, 2026
a7b90b5
Fix Ring switch setup crash on unknown doorbell chime type (#182375)
rasadregmi Sep 19, 2026
88f86be
Add instance ID to mobile_app get_config response (#182644)
bgoncal Sep 19, 2026
5532e36
Bump uiprotect to 16.12.0 (#182670)
RaHehl Sep 19, 2026
f48794a
Allow image_upload in the media selector config (#180452)
parkghost Sep 19, 2026
a8d46db
alexa_devices: add person details to voice event (#182410)
jamesonuk Sep 19, 2026
70671b8
alexa_devices: remove stale values from known entities (#182401)
jamesonuk Sep 19, 2026
3f5a758
Migrate GitHub Actions updates from Dependabot to Renovate (#182668)
frenck Sep 19, 2026
d91529a
Drop UniFi Protect helpers that uiprotect 16.12.0 now provides (#182688)
RaHehl Sep 19, 2026
b13c17b
Bump pySmartThings to 4.0.3 (#182690)
joostlek Sep 19, 2026
40fcd7d
Bump nyt_games to 1.0.0 (#182689)
joostlek Sep 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 0 additions & 21 deletions .github/dependabot.yml

This file was deleted.

39 changes: 38 additions & 1 deletion .github/renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"dockerfile",
"npm",
"custom.regex",
"github-actions",
"homeassistant-manifest"
],

Expand Down Expand Up @@ -55,7 +56,7 @@
],

"minimumReleaseAge": "7 days",
"prConcurrentLimit": 10,
"prConcurrentLimit": 0,
"prHourlyLimit": 2,
"schedule": ["before 6am"],

Expand Down Expand Up @@ -183,6 +184,22 @@
"enabled": true,
"labels": ["dependency"]
},
{
"description": "GitHub Actions used in our workflows (allowlisted). Runner labels and action version inputs are deliberately left out.",
"matchManagers": ["github-actions"],
"matchDepTypes": ["action", "workflow", "docker", "container", "service"],
"enabled": true,
"labels": ["dependency", "github_actions"]
},
{
"description": "Managed by gh aw compile. Version-locked to the gh-aw compiler; do not bump.",
"matchPackageNames": [
"github/gh-aw-actions",
"/^github\\/gh-aw-actions\\//",
"/^ghcr\\.io\\/github\\/gh-aw/"
],
"enabled": false
},
{
"description": "pnpm version pinned in the E2E tests packageManager field (allowlisted)",
"matchManagers": ["npm"],
Expand All @@ -192,6 +209,14 @@
"enabled": true,
"labels": ["dependency"]
},
{
"description": "Playwright pinned in the E2E tests (allowlisted)",
"matchManagers": ["npm"],
"matchFileNames": ["tests/e2e/package.json"],
"matchPackageNames": ["@playwright/test"],
"enabled": true,
"labels": ["dependency"]
},
{
"description": "For types-* stubs, only allow patch updates. Major/minor bumps track the upstream runtime package version and must be manually coordinated with the corresponding pin.",
"matchPackageNames": ["/^types-/"],
Expand Down Expand Up @@ -247,6 +272,18 @@
"groupName": "pylint",
"groupSlug": "pylint"
},
{
"description": "Group the github/codeql-action subactions into one PR (they share a release)",
"matchPackageNames": ["/^github\\/codeql-action(?:\\/|$)/"],
"groupName": "github/codeql-action",
"groupSlug": "codeql-action"
},
{
"description": "Group the actions/cache subactions into one PR (they share a release)",
"matchPackageNames": ["/^actions\\/cache(?:\\/|$)/"],
"groupName": "actions/cache",
"groupSlug": "actions-cache"
},
{
"description": "Group go2rtc Dockerfile pin with const.py RECOMMENDED_VERSION into one PR",
"matchPackageNames": ["ghcr.io/alexxit/go2rtc"],
Expand Down
7 changes: 4 additions & 3 deletions homeassistant/components/alexa_devices/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class AmazonBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Alexa Devices binary sensor entity description."""

is_on_fn: Callable[[AmazonDevice, str], bool]
is_supported: Callable[[AmazonDevice, str], bool] = lambda device, key: True
is_supported_fn: Callable[[AmazonDevice, str], bool] = lambda device, key: True
is_available_fn: Callable[[AmazonDevice, str], bool] = lambda device, key: True


Expand All @@ -49,7 +49,7 @@ class AmazonBinarySensorEntityDescription(BinarySensorEntityDescription):
is_on_fn=lambda device, key: bool(
device.sensors[key].value != SENSOR_STATE_OFF
),
is_supported=lambda device, key: device.sensors.get(key) is not None,
is_supported_fn=lambda device, key: device.sensors.get(key) is not None,
is_available_fn=lambda device, key: (
device.online
and (sensor := device.sensors.get(key)) is not None
Expand Down Expand Up @@ -127,14 +127,15 @@ async def async_setup_entry(

def _check_device() -> None:
current_devices = set(coordinator.data)
known_devices.intersection_update(current_devices)
new_devices = current_devices - known_devices
if new_devices:
known_devices.update(new_devices)
async_add_entities(
AmazonBinarySensorEntity(coordinator, serial_num, sensor_desc)
for sensor_desc in BINARY_SENSORS
for serial_num in new_devices
if sensor_desc.is_supported(
if sensor_desc.is_supported_fn(
coordinator.data[serial_num], sensor_desc.key
)
)
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/alexa_devices/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ async def async_setup_entry(

def _check_routines_devices() -> None:
current_routines = set(coordinator.api.routines)
known_routines.intersection_update(current_routines)
new_routines = current_routines - known_routines
if new_routines:
known_routines.update(new_routines)
Expand All @@ -59,6 +60,7 @@ def _check_routines_devices() -> None:
)

current_devices = set(coordinator.data)
known_devices.intersection_update(current_devices)
new_devices = current_devices - known_devices
if new_devices:
known_devices.update(new_devices)
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/alexa_devices/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ async def async_setup_entry(

def _check_device() -> None:
current_devices = set(coordinator.data)
known_devices.intersection_update(current_devices)
new_devices = current_devices - known_devices
if new_devices:
known_devices.update(new_devices)
Expand Down Expand Up @@ -96,6 +97,8 @@ def _handle_coordinator_update(self) -> None:
"intent": vocal_record.intent,
"voice_command": vocal_record.title,
"voice_reply": vocal_record.sub_title,
"person_first_name": vocal_record.person_first_name,
"person_type": vocal_record.person_type,
},
)
self.async_write_ha_state()
1 change: 1 addition & 0 deletions homeassistant/components/alexa_devices/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def _check_device() -> None:
"""Add entities for newly discovered devices."""
new_entities: list[AlexaDevicesMediaPlayer] = []

known_devices.intersection_update(coordinator.data)
for serial_num, device in coordinator.data.items():
if serial_num in known_devices or not device.media_player_supported:
continue
Expand Down
7 changes: 4 additions & 3 deletions homeassistant/components/alexa_devices/notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
class AmazonNotifyEntityDescription(NotifyEntityDescription):
"""Alexa Devices notify entity description."""

is_supported: Callable[[AmazonDevice], bool] = lambda _device: True
is_supported_fn: Callable[[AmazonDevice], bool] = lambda _device: True
is_available_fn: Callable[[AmazonDevice], bool] = lambda _device: True
method: Callable[[AmazonEchoApi, AmazonDevice, str], Awaitable[None]]
subkey: str
Expand All @@ -33,7 +33,7 @@ class AmazonNotifyEntityDescription(NotifyEntityDescription):
key="speak",
translation_key="speak",
subkey="AUDIO_PLAYER",
is_supported=lambda _device: _device.device_family != SPEAKER_GROUP_FAMILY,
is_supported_fn=lambda _device: _device.device_family != SPEAKER_GROUP_FAMILY,
method=lambda api, device, message: api.call_alexa_speak(device, message),
),
AmazonNotifyEntityDescription(
Expand Down Expand Up @@ -63,6 +63,7 @@ async def async_setup_entry(

def _check_device() -> None:
current_devices = set(coordinator.data)
known_devices.intersection_update(current_devices)
new_devices = current_devices - known_devices
if new_devices:
known_devices.update(new_devices)
Expand All @@ -71,7 +72,7 @@ def _check_device() -> None:
for sensor_desc in NOTIFY
for serial_num in new_devices
if sensor_desc.subkey in coordinator.data[serial_num].capabilities
and sensor_desc.is_supported(coordinator.data[serial_num])
and sensor_desc.is_supported_fn(coordinator.data[serial_num])
)

_check_device()
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/alexa_devices/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ async def async_setup_entry(

def _check_device() -> None:
current_devices = set(coordinator.data)
known_devices.intersection_update(current_devices)
new_devices = current_devices - known_devices
if new_devices:
known_devices.update(new_devices)
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/alexa_devices/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ async def async_setup_entry(

def _check_device() -> None:
current_devices = set(coordinator.data)
known_devices.intersection_update(current_devices)
new_devices = current_devices - known_devices
if new_devices:
known_devices.update(new_devices)
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/alexa_devices/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ async def async_setup_entry(

def _check_device() -> None:
current_devices = set(coordinator.data)
known_devices.intersection_update(current_devices)
new_devices = current_devices - known_devices
if new_devices:
known_devices.update(new_devices)
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/alexa_devices/todo.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ async def async_setup_entry(

def _check_lists() -> None:
current_list_ids = {todo_list.id for todo_list in coordinator.api.todo_lists}
known_list_ids.intersection_update(current_list_ids)
new_list_ids = current_list_ids - known_list_ids
if new_list_ids:
known_list_ids.update(new_list_ids)
Expand Down
7 changes: 5 additions & 2 deletions homeassistant/components/cync/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class CyncLightEntity(CyncBaseEntity, LightEntity):
"""Representation of a Cync light."""

_attr_color_mode = ColorMode.ONOFF
_attr_supported_color_modes: set[ColorMode]
_attr_min_color_temp_kelvin = 2000
_attr_max_color_temp_kelvin = 7000
_attr_translation_key = "light"
Expand Down Expand Up @@ -129,10 +130,12 @@ def color_mode(self) -> ColorMode:
and self._device.color_mode == 254
):
return ColorMode.RGB
if self._device.supports_capability(CyncCapability.DIMMING):
if ColorMode.BRIGHTNESS in self._attr_supported_color_modes:
return ColorMode.BRIGHTNESS
if ColorMode.ONOFF in self._attr_supported_color_modes:
return ColorMode.ONOFF

return ColorMode.ONOFF
return ColorMode.UNKNOWN

@override
async def async_turn_on(self, **kwargs: Any) -> None:
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/flo/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@
FLO_AWAY = "away"
FLO_SLEEP = "sleep"
FLO_MODES = [FLO_HOME, FLO_AWAY, FLO_SLEEP]

# Valves without a water-temperature sensor report a fixed placeholder instead of
# omitting tempF. No domestic supply reaches boiling, so a reading at or above
# this is a sentinel rather than a measurement.
IMPLAUSIBLE_WATER_TEMP_F = 212.0
11 changes: 7 additions & 4 deletions homeassistant/components/flo/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util import dt as dt_util

from .const import DOMAIN, LOGGER
from .const import DOMAIN, IMPLAUSIBLE_WATER_TEMP_F, LOGGER

type FloConfigEntry = ConfigEntry[FloRuntimeData]

Expand Down Expand Up @@ -144,9 +144,12 @@ def current_psi(self) -> float:
return self._device_information["telemetry"]["current"]["psi"]

@property
def temperature(self) -> float:
"""Return the current temperature in degrees F."""
return self._device_information["telemetry"]["current"]["tempF"]
def temperature(self) -> float | None:
"""Return the current temperature in degrees F, or None if not measured."""
temperature = self._device_information["telemetry"]["current"]["tempF"]
if temperature is None or temperature >= IMPLAUSIBLE_WATER_TEMP_F:
return None
return temperature

@property
def humidity(self) -> float:
Expand Down
23 changes: 18 additions & 5 deletions homeassistant/components/hassio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,15 +186,28 @@ async def _async_get_or_create_supervisor_user(
if user is None and legacy_user_id is not None:
user = await hass.auth.async_get_user(legacy_user_id)

if user is None:
# The storage naming the Supervisor user may have been lost. Reuse an
# existing Supervisor system user instead of creating a duplicate.
user = next(
(
existing
for existing in await hass.auth.async_get_users()
if existing.system_generated and existing.name == HASSIO_USER_NAME
),
None,
)

if user is None:
user = await hass.auth.async_create_system_user(
HASSIO_USER_NAME, group_ids=[GROUP_ID_ADMIN]
)
if entry is not None:
hass.config_entries.async_update_entry(
entry,
data={**entry.data, ENTRY_DATA_USER: user.id},
)

if entry is not None and entry.data.get(ENTRY_DATA_USER) != user.id:
hass.config_entries.async_update_entry(
entry,
data={**entry.data, ENTRY_DATA_USER: user.id},
)

# Migrate old Hass.io users to be admin.
if not user.is_admin:
Expand Down
37 changes: 9 additions & 28 deletions homeassistant/components/imou/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,41 +102,22 @@ async def _async_update_data(self) -> None:

try:
async with asyncio.timeout(UPDATE_TIMEOUT):
results = await asyncio.gather(
*(
self._device_manager.async_update_device_status(device)
for device in devices
),
return_exceptions=True,
)
await self._device_manager.async_update_devices_status(devices)
except TimeoutError as err:
raise UpdateFailed(f"Timeout while fetching data: {err}") from err

failures: list[Exception] = []
for device, result in zip(devices, results, strict=True):
if isinstance(result, BaseException) and not isinstance(result, Exception):
# Propagate CancelledError and other BaseExceptions instead of
# swallowing them as a regular device failure.
raise result
if not isinstance(result, Exception):
continue
device_key = imou_device_identifier(device)
_LOGGER.warning(
"Error updating status for Imou device %s: %s",
device_key,
result,
)
failures.append(result)
if failures and len(failures) == len(devices):
raise UpdateFailed(
f"Error updating Imou devices: {failures[0]}"
) from failures[0]
except InvalidAppIdOrSecretException as err:
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="invalid_auth",
) from err
except ImouException as err:
raise UpdateFailed(f"Error updating Imou devices: {err}") from err

def _async_add_remove_devices(self, fresh_by_key: dict[str, ImouHaDevice]) -> None:
"""Add new devices, remove devices no longer in the account.

This only tracks which devices exist on the account; per-device state
is updated in place by `async_update_device_status`, so devices that
is updated in place by `async_update_devices_status`, so devices that
remain on the account keep their existing object and are not replaced.
"""
if not self._devices_initialized:
Expand Down
Loading
Loading