Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
6dc4377
Undeprecate http server_host configuration (#177536)
edenhaus Jul 29, 2026
4cede9b
Schlage: refactor tests to use snapshot_platform and factory fixture …
dknowles2 Jul 29, 2026
ab8454b
Add ViAir 300F humidity and temperature sensors to Vicare (#176777)
gresie Jul 29, 2026
95ac9db
Explain why a Comet Blue device could not be found (#176768)
rikroe Jul 29, 2026
9a93ebd
Add image platform for DoorBird last motion and last ring (#173120)
mattias-arrelid Jul 29, 2026
7509d18
Add power control to Daikin zone climates (#176764)
barneyonline Jul 29, 2026
eb32de1
Add zoned cleaning service for Roborock (#172673)
Thomas55555 Jul 29, 2026
1dd9181
Add restore to light template entities (#176553)
Petro31 Jul 29, 2026
ef4bf9f
Add tilt support to MySensors cover platform (#173845)
hypnosiss Jul 29, 2026
d94e20b
Add restore to lock template entities (#176571)
Petro31 Jul 29, 2026
e97766a
Add restore state to vacuum template entities (#176718)
Petro31 Jul 29, 2026
01cb78e
Update ruff to 0.16.0 (#177534)
renovate[bot] Jul 29, 2026
1446529
Bump github/codeql-action/analyze from 4.37.2 to 4.37.3 (#177524)
dependabot[bot] Jul 29, 2026
beb0a8a
Add Room Average Temperature sensor to Lyric integration (#177063)
clutch2sft Jul 29, 2026
dee4aff
Add async_will_remove_from_hass to pylint super call checker (#175986)
gjohansson-ST Jul 29, 2026
0355339
Fix UnboundLocalError in state condition with an empty state value (#…
ngoctnq Jul 29, 2026
6db26ee
Promote energieleser integration to platinum quality scale (#176617)
amitkio Jul 29, 2026
c23b6e9
Add more button entities to LED Infrared (#177400)
tr4nt0r Jul 29, 2026
c53c944
Clarify common config flow already_in_progress abort message (#176981)
Swamp-Ig Jul 29, 2026
f03f9bd
Fix Schlage test snapshot (#177548)
MartinHjelmare Jul 29, 2026
d6032ba
Fix device renaming in acmeda (#176980)
emontnemery Jul 29, 2026
819759f
Add support for camera preloading to go2rtc (#158470)
edenhaus Jul 29, 2026
77e9fe3
Enable ruff rule RUF036 (none-not-at-end-of-union) (#177543)
edenhaus Jul 29, 2026
f3c560e
Add Switch platform for Harbor (#177511)
Lash-L Jul 29, 2026
6845d09
Add cleaning solution, sewage box and water sink lifespan sensors to …
L-pin Jul 29, 2026
9f7a25c
Add Ecovacs edge trimmer lifespan entities (#175471)
monsivar Jul 29, 2026
f09d6c1
Add restore to select template entities (#176692)
Petro31 Jul 29, 2026
2f921e4
Bump neopool-modbus to 4.5.1 (#177549)
svasek Jul 29, 2026
7a8f476
Enable ruff rule FURB192 (sorted-min-max) (#177544)
edenhaus Jul 29, 2026
d4cbeaa
Migrate UniFi Protect detection sensors to the public API and add cat…
RaHehl Jul 29, 2026
e08143a
Do not raise the UniFi Protect stream repair when streams cannot be r…
RaHehl Jul 29, 2026
2551990
Raise nobo_hub quality scale to platinum (#176612)
oyvindwe Jul 29, 2026
6f87b39
Add missing standing fan preset icons for SwitchBot (#177537)
piitaya Jul 29, 2026
f78ad3e
Fix SmartThings crash when threeAxis attribute is None (#176127)
TowyTowy Jul 29, 2026
536cee8
Migrate ruuvi_gateway to use entry.runtime_data (#176139)
David-Wu1119 Jul 29, 2026
7dee401
Migrate ruuvitag_ble to use entry.runtime_data (#176141)
David-Wu1119 Jul 29, 2026
c9428da
Use proper command set for Edifier R2000DB and R2730DB (#177472)
abmantis Jul 29, 2026
3f188ef
Register legacy notify action before setup test in Notifications for …
tr4nt0r Jul 29, 2026
c498d4f
Move service registration to async_setup in Todoist (#175759)
some-random-climber Jul 29, 2026
34114ba
Make IOMeter use SSE (#172364)
torben-iometer Jul 29, 2026
9f38e0f
Increase Modern Forms scan interval to reduce WiFi lockups (#177508)
wonderslug Jul 29, 2026
f2381de
Update frontend to 20260729.0 (#177555)
bramkragten Jul 29, 2026
5882229
Adapt tuya to new device registry API (#176955)
emontnemery Jul 29, 2026
60b8e8c
Adapt dhcp to new device registry API (#176982)
emontnemery Jul 29, 2026
deb94f5
Adapt homekit_controller to new device registry API (#176985)
emontnemery Jul 29, 2026
e5cfeab
Adapt unifiprotect to new device registry API (#176957)
emontnemery Jul 29, 2026
2b11252
Catch missing restore keys for template cover, device_tracker, and fa…
Petro31 Jul 29, 2026
3a924df
Correct description of "shopping_list.complete_item" action (#175534)
jamesmyatt Jul 29, 2026
5b8527f
Fix CI (#177557)
joostlek Jul 29, 2026
847db75
Bump version to 2026.9.0.dev0 (#177558)
bramkragten Jul 29, 2026
895f977
daikin: map Empty values exception to cannot_connect in config flow (…
Rodrigorm33 Jul 29, 2026
f5a6515
Add speech speed option to Fish Audio TTS (#174830)
helitik Jul 29, 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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ on:
env:
CACHE_VERSION: 4
MYPY_CACHE_VERSION: 1
HA_SHORT_VERSION: "2026.8"
HA_SHORT_VERSION: "2026.9"
ADDITIONAL_PYTHON_VERSIONS: "[]"
# 10.3 is the oldest supported version
# - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022)
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ jobs:
languages: python

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@e0647621c2984b5ed2f768cb892365bf2a616ad1 # v4.37.2
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
with:
category: "/language:python"
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.22
rev: v0.16.0
hooks:
- id: ruff-check
args:
Expand Down
2 changes: 2 additions & 0 deletions .strict-typing
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ homeassistant.components.elgato.*
homeassistant.components.elkm1.*
homeassistant.components.emulated_hue.*
homeassistant.components.energenie_power_sockets.*
homeassistant.components.energieleser.*
homeassistant.components.energy.*
homeassistant.components.energyid.*
homeassistant.components.energyzero.*
Expand Down Expand Up @@ -408,6 +409,7 @@ homeassistant.components.nfandroidtv.*
homeassistant.components.nightscout.*
homeassistant.components.nissan_leaf.*
homeassistant.components.no_ip.*
homeassistant.components.nobo_hub.*
homeassistant.components.nordpool.*
homeassistant.components.notify.*
homeassistant.components.notion.*
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/acmeda/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ async def update_devices(
for api_item in api.values():
# Update Device name
device = dev_registry.async_get_device_by_identifier(
(DOMAIN, api_item.id), config_entry.entry_id
(DOMAIN, str(api_item.id)), config_entry.entry_id
)
if device is not None:
dev_registry.async_update_device(
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/ai_task/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,8 @@ async def async_load(self) -> None:
def async_set_preferences(
self,
*,
gen_data_entity_id: str | None | UndefinedType = UNDEFINED,
gen_image_entity_id: str | None | UndefinedType = UNDEFINED,
gen_data_entity_id: str | UndefinedType | None = UNDEFINED,
gen_image_entity_id: str | UndefinedType | None = UNDEFINED,
) -> None:
"""Set the preferences."""
changed = False
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/arwn/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def async_sensor_event_received(msg: mqtt.ReceiveMessage) -> None:
try:
event = json_loads_object(msg.payload)
device = parse_message(msg.topic, event)
except Exception: # noqa: BLE001
except Exception:
_LOGGER.debug(
"Failed to parse ARWN message on topic %s",
msg.topic,
Expand Down
14 changes: 7 additions & 7 deletions homeassistant/components/assist_pipeline/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,13 +342,13 @@ async def async_update_pipeline(
conversation_language: str | UndefinedType = UNDEFINED,
language: str | UndefinedType = UNDEFINED,
name: str | UndefinedType = UNDEFINED,
stt_engine: str | None | UndefinedType = UNDEFINED,
stt_language: str | None | UndefinedType = UNDEFINED,
tts_engine: str | None | UndefinedType = UNDEFINED,
tts_language: str | None | UndefinedType = UNDEFINED,
tts_voice: str | None | UndefinedType = UNDEFINED,
wake_word_entity: str | None | UndefinedType = UNDEFINED,
wake_word_id: str | None | UndefinedType = UNDEFINED,
stt_engine: str | UndefinedType | None = UNDEFINED,
stt_language: str | UndefinedType | None = UNDEFINED,
tts_engine: str | UndefinedType | None = UNDEFINED,
tts_language: str | UndefinedType | None = UNDEFINED,
tts_voice: str | UndefinedType | None = UNDEFINED,
wake_word_entity: str | UndefinedType | None = UNDEFINED,
wake_word_id: str | UndefinedType | None = UNDEFINED,
prefer_local_intents: bool | UndefinedType = UNDEFINED,
) -> None:
"""Update a pipeline."""
Expand Down
69 changes: 63 additions & 6 deletions homeassistant/components/camera/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ async def _async_get_stream_image(
height: int | None = None,
wait_for_next_keyframe: bool = False,
) -> bytes | None:
if (provider := camera._webrtc_provider) and ( # noqa: SLF001
if (provider := camera.webrtc_provider) and (
image := await provider.async_get_image(camera, width=width, height=height)
) is not None:
return image
Expand Down Expand Up @@ -407,6 +407,21 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return await hass.data[DATA_COMPONENT].async_unload_entry(entry)


async def _async_call_webrtc_provider(
coro: Coroutine[Any, Any, None], description: str, entity_id: str
) -> None:
"""Await a WebRTC provider callback without letting exceptions propagate.

Provider callbacks can do I/O and must not break camera setup or removal.
"""
try:
await coro
except HomeAssistantError as ex:
_LOGGER.error("Error %s %s: %s", description, entity_id, ex)
except Exception:
_LOGGER.exception("Unexpected error %s %s", description, entity_id)


CACHED_PROPERTIES_WITH_ATTR_ = {
"brand",
"frame_interval",
Expand Down Expand Up @@ -514,6 +529,12 @@ def available(self) -> bool:
return False
return super().available

@final
@property
def webrtc_provider(self) -> CameraWebRTCProvider | None:
"""Return the WebRTC provider."""
return self._webrtc_provider

async def async_create_stream(self) -> Stream | None:
"""Create a Stream for stream_source."""
# There is at most one stream (a decode worker) per camera
Expand Down Expand Up @@ -683,6 +704,18 @@ async def async_internal_added_to_hass(self) -> None:
self.__supports_stream = self.supported_features & CameraEntityFeature.STREAM
await self.async_refresh_providers(write_state=False)

@override
async def async_internal_will_remove_from_hass(self) -> None:
"""Run when entity will be removed from hass."""
if self._webrtc_provider:
await _async_call_webrtc_provider(
self._webrtc_provider.async_unregister_camera(self),
"unregistering WebRTC provider for",
self.entity_id,
)
self._webrtc_provider = None
await super().async_internal_will_remove_from_hass()

async def async_refresh_providers(self, *, write_state: bool = True) -> None:
"""Determine if any of the registered providers are suitable for this entity.

Expand All @@ -699,11 +732,27 @@ async def async_refresh_providers(self, *, write_state: bool = True) -> None:
async_get_supported_provider
)

if old_provider != new_provider:
self._webrtc_provider = new_provider
self._invalidate_camera_capabilities_cache()
if write_state:
self.async_write_ha_state()
if old_provider == new_provider:
return

if old_provider:
await _async_call_webrtc_provider(
old_provider.async_unregister_camera(self),
"unregistering WebRTC provider for",
self.entity_id,
)

if new_provider:
await _async_call_webrtc_provider(
new_provider.async_register_camera(self),
"registering WebRTC provider for",
self.entity_id,
)

self._webrtc_provider = new_provider
self._invalidate_camera_capabilities_cache()
if write_state:
self.async_write_ha_state()

async def _async_get_supported_webrtc_provider[_T](
self, fn: Callable[[HomeAssistant, Camera], Coroutine[None, None, _T | None]]
Expand Down Expand Up @@ -969,6 +1018,14 @@ async def websocket_update_prefs(
_LOGGER.error("Error setting camera preferences: %s", ex)
connection.send_error(msg["id"], "update_failed", str(ex))
else:
if (camera := hass.data[DATA_COMPONENT].get_entity(entity_id)) and (
provider := camera.webrtc_provider
):
await _async_call_webrtc_provider(
provider.async_on_camera_prefs_update(camera),
"notifying WebRTC provider of preferences update for",
entity_id,
)
connection.send_result(msg["id"], entity_prefs)


Expand Down
14 changes: 13 additions & 1 deletion homeassistant/components/camera/webrtc.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ async def async_on_webrtc_candidate(
@callback
def async_close_session(self, session_id: str) -> None:
"""Close the session."""
return ## This is an optional method so we need a default here.
return # This is an optional method so we need a default here.

async def async_get_image(
self,
Expand All @@ -156,6 +156,18 @@ async def async_get_image(
"""Get an image from the camera."""
return None

async def async_register_camera(self, camera: Camera) -> None:
"""Will be called when the provider is registered for a camera."""
return # This is an optional method so we need a default here.

async def async_unregister_camera(self, camera: Camera) -> None:
"""Will be called when the provider is unregistered for a camera."""
return # This is an optional method so we need a default here.

async def async_on_camera_prefs_update(self, camera: Camera) -> None:
"""Will be called when the camera preferences are updated."""
return # This is an optional method so we need a default here.


@callback
def async_register_webrtc_provider(
Expand Down
6 changes: 3 additions & 3 deletions homeassistant/components/cloud/prefs.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,12 @@ async def async_update(
google_connected: bool | UndefinedType = UNDEFINED,
google_enabled: bool | UndefinedType = UNDEFINED,
google_report_state: bool | UndefinedType = UNDEFINED,
google_secure_devices_pin: str | None | UndefinedType = UNDEFINED,
google_secure_devices_pin: str | UndefinedType | None = UNDEFINED,
google_settings_version: int | UndefinedType = UNDEFINED,
remote_allow_remote_enable: bool | UndefinedType = UNDEFINED,
remote_domain: str | None | UndefinedType = UNDEFINED,
remote_domain: str | UndefinedType | None = UNDEFINED,
onboarded_items: list[str] | UndefinedType = UNDEFINED,
onboarding_postponed_until: str | None | UndefinedType = UNDEFINED,
onboarding_postponed_until: str | UndefinedType | None = UNDEFINED,
remote_enabled: bool | UndefinedType = UNDEFINED,
tts_default_voice: tuple[str, str] | UndefinedType = UNDEFINED,
) -> None:
Expand Down
44 changes: 38 additions & 6 deletions homeassistant/components/daikin/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,11 @@ class DaikinZoneClimate(DaikinEntity, ClimateEntity):

_attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_has_entity_name = True
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
_attr_supported_features = (
ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.TURN_OFF
| ClimateEntityFeature.TURN_ON
)
_attr_target_temperature_step = 1

def __init__(self, coordinator: DaikinCoordinator, zone_id: int) -> None:
Expand All @@ -403,18 +407,26 @@ def __init__(self, coordinator: DaikinCoordinator, zone_id: int) -> None:
zone_name = self.device.zones[self._zone_id][0]
self._attr_name = f"{zone_name} temperature"

@property
def _main_hvac_mode(self) -> HVACMode:
"""Return the main unit HVAC mode."""
daikin_mode = self.device.represent(HA_ATTR_TO_DAIKIN[ATTR_HVAC_MODE])[1]
return DAIKIN_TO_HA_STATE.get(daikin_mode, HVACMode.HEAT_COOL)

@property
@override
def hvac_modes(self) -> list[HVACMode]:
"""Return the hvac modes (mirrors the main unit)."""
return [self.hvac_mode]
return [self._main_hvac_mode]

@property
@override
def hvac_mode(self) -> HVACMode:
"""Return the current HVAC mode."""
daikin_mode = self.device.represent(HA_ATTR_TO_DAIKIN[ATTR_HVAC_MODE])[1]
return DAIKIN_TO_HA_STATE.get(daikin_mode, HVACMode.HEAT_COOL)
main_mode = self._main_hvac_mode
if main_mode == HVACMode.OFF or self.device.zones[self._zone_id][1] != "1":
return HVACMode.OFF
return main_mode

@property
@override
Expand All @@ -427,7 +439,7 @@ def hvac_action(self) -> HVACAction | None:
def target_temperature(self) -> float | None:
"""Return the zone target temperature for the active mode."""
heating, cooling = _zone_temperature_lists(self.device)
mode = self.hvac_mode
mode = self._main_hvac_mode
if mode == HVACMode.HEAT:
return _zone_temperature_from_list(heating, self._zone_id)
if mode == HVACMode.COOL:
Expand Down Expand Up @@ -499,7 +511,7 @@ async def async_set_temperature(self, **kwargs: Any) -> None:
if target is None:
raise _zone_error("zone_parameters_unavailable")

mode = self.hvac_mode
mode = self._main_hvac_mode
if mode == HVACMode.HEAT:
zone_key = DAIKIN_ZONE_TEMP_HEAT
elif mode == HVACMode.COOL:
Expand All @@ -515,6 +527,26 @@ async def async_set_temperature(self, **kwargs: Any) -> None:

await self.coordinator.async_request_refresh()

@override
async def async_turn_on(self) -> None:
"""Turn the zone on."""
await self.device.set_zone(self._zone_id, "zone_onoff", "1")
await self.coordinator.async_refresh()

@override
async def async_turn_off(self) -> None:
"""Turn the zone off."""
await self.device.set_zone(self._zone_id, "zone_onoff", "0")
await self.coordinator.async_refresh()

@override
async def async_toggle(self) -> None:
"""Toggle the zone."""
if self.device.zones[self._zone_id][1] == "1":
await self.async_turn_off()
else:
await self.async_turn_on()

@override
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Disallow changing HVAC mode via zone climate."""
Expand Down
5 changes: 2 additions & 3 deletions homeassistant/components/daikin/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,11 @@ async def _create_device(
data_schema=self.schema,
errors={"base": "invalid_auth"},
)
except DaikinException as daikin_exp:
_LOGGER.error(daikin_exp)
except DaikinException:
return self.async_show_form(
step_id="user",
data_schema=self.schema,
errors={"base": "unknown"},
errors={"base": "cannot_connect"},
)
except Exception:
_LOGGER.exception("Unexpected error creating device")
Expand Down
15 changes: 9 additions & 6 deletions homeassistant/components/dhcp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,14 +233,17 @@ def async_process_client(
registered_devices_domains = matchers.registered_devices_domains

dev_reg = dr.async_get(self.hass)
if device := dev_reg.async_get_device(
# Several config entries can each own a device for the same MAC, so check every
# matching device, not just the first, or a shared-MAC integration is dropped.
for device in dev_reg.async_get_devices(
connections={(CONNECTION_NETWORK_MAC, formatted_mac)}
):
for entry_id in device.config_entries:
if (
entry := self.hass.config_entries.async_get_entry(entry_id)
) and entry.domain in registered_devices_domains:
matched_domains.add(entry.domain)
if (
entry := self.hass.config_entries.async_get_entry(
device.config_entry_id
)
) and entry.domain in registered_devices_domains:
matched_domains.add(entry.domain)

oui = uppercase_mac[:6]
lowercase_hostname_first_char = (
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/doorbird/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from homeassistant.const import Platform

DOMAIN = "doorbird"
PLATFORMS = [Platform.BUTTON, Platform.CAMERA, Platform.EVENT]
PLATFORMS = [Platform.BUTTON, Platform.CAMERA, Platform.EVENT, Platform.IMAGE]

CONF_EVENTS = "events"
MANUFACTURER = "Bird Home Automation Group"
Expand Down
Loading
Loading