Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
a33fc1b
Update pystiebeleltron to 0.8.0 (#182618)
ThyMYthOS Sep 19, 2026
a11edea
Deprecate ToolResultContent.tool_result (#182551)
balloob Sep 19, 2026
d59ebdb
Bump env-canada to 0.20.3 (#182650)
michaeldavie Sep 19, 2026
7014cea
Bump python-xbox to 0.3.0 (#182643)
tr4nt0r Sep 19, 2026
4fdcc2a
Add codeowner to SMTP integration (#182638)
tr4nt0r Sep 19, 2026
69a5efb
Mark disconnected UniFi Protect sirens and relays unavailable (#182635)
RaHehl Sep 19, 2026
744fd40
Bump midea-local to 12.1.0 (#182624)
rokam Sep 19, 2026
019eeca
Bump open-garage to 0.3.1 (#182626)
ziptbm Sep 19, 2026
9c21997
Upgrade to quality scale: silver (#182510)
karwosts Sep 19, 2026
a1ab6a3
Gatus: Mark entity translations as complete (#182479)
TN-1 Sep 19, 2026
fc9713a
Assert the translation metadata in the kodi notify error test (#182605)
smitec11 Sep 19, 2026
c1bce6e
Add groups configuration for CodeQL action (#182418)
silamon Sep 19, 2026
529a409
Bump diematic-modbus to 0.8.0 (#182597)
DaanVervacke Sep 19, 2026
c1ac51d
Add sensoroverride to ViCare ventilation reason options (#182378)
misterboe Sep 19, 2026
369a011
Use glass break device class in HomematicIP Cloud (#182240)
catellie Sep 19, 2026
bf34679
Remove hardcoded tool name from GetLiveContext prompt (#182111)
HariomGupta0 Sep 19, 2026
e0008c3
Add the Current consumption attribute to the D-Link smart power plug …
matejpramnik Sep 19, 2026
d307d49
test(ecobee): use timezone-aware now in switch tests (#181987)
eseverson Sep 19, 2026
8385ee1
Add image file to testingconfig (#181959)
tr4nt0r Sep 19, 2026
6f3eb0b
Fix iCloud device tracker stale location by requesting an active loca…
radzio Sep 19, 2026
9204085
Use bulk edit API to delete OurGroceries items (#182402)
ljmerza Sep 19, 2026
3c3841f
Config entry integration methods returns None (#182429)
gjohansson-ST Sep 19, 2026
6bc9bf8
Add required_domains to the cover open/close intents (#181892)
sappafrancesco Sep 19, 2026
fc2e365
Stop forcing the Blink refresh on every poll (#182639)
Dextheking1 Sep 19, 2026
ce976b9
Isolate CI-only hassfest tooling from the HA wheel index (#181782)
klaasnicolaas Sep 19, 2026
2af8764
Add required_domains to the cover open/close intents (#181892)
sappafrancesco Sep 19, 2026
f6e830c
Update IQS to gold for Midea (#182580)
chemelli74 Sep 19, 2026
94432b5
Preserve Ring listener credentials during reauth and reconfigure (#18…
thomasgregg Sep 19, 2026
e0010d3
Update ical to 14.2.0 (#182658)
renovate[bot] Sep 19, 2026
94594f5
Reset Tesla Fleet energy history interval after rate limit backoff (#…
Bre77 Sep 19, 2026
36cd5c6
Use glass break device class in SimpliSafe (#182241)
catellie Sep 19, 2026
54daa0f
Add hot water push functionality to opentherm_gw (#182299)
mvn23 Sep 19, 2026
7692013
Use glass break device class in Z-Wave JS (#182243)
catellie Sep 19, 2026
f31b3e2
Fix Matter fan and light dividing by zero on out-of-spec device range…
frenck Sep 19, 2026
d973cfe
Add delay_on and delay_off when configuring a binary sensor template …
Petro31 Sep 19, 2026
279b124
Resolve integration brand images before brand images (#182217)
joostlek Sep 19, 2026
2809e5f
Bump anyio from 4.13.0 to 4.14.2 (#182665)
dependabot[bot] Sep 19, 2026
a72ca7b
Add brightness_pct beside brightness in the LLM live context (#182571)
jdavidbush Sep 19, 2026
c85031d
Expose listed emulated_hue entities when expose_by_default is off (#1…
hktitof Sep 19, 2026
af4a191
Add MPPT entities for Fronius inverters with more than 4 modules (#18…
farmio Sep 19, 2026
3199454
Use glass break device class in Hive (#182239)
catellie Sep 19, 2026
fee5280
Handle unavailable UniFi configuration endpoint (#182574)
Kane610 Sep 19, 2026
2329db5
Track devolo Home Network Wi-Fi clients via the entity registry (#182…
Shutgun 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
4 changes: 4 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ updates:
- github_actions
cooldown:
default-days: 7
groups:
codeql-action:
patterns:
- "github/codeql-action/*"
ignore:
# Managed by gh aw compile. Version-locked to the gh-aw compiler; do not bump.
- dependency-name: "github/gh-aw-actions/**"
Expand Down
2 changes: 2 additions & 0 deletions CODEOWNERS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion homeassistant/components/blink/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ async def _async_setup(self):
async def _async_update_data(self) -> dict[str, Any]:
"""Async update wrapper."""
try:
return await self.api.refresh(force=True)
# Do not force the refresh. BlinkPy turns a forced refresh into
# force_cache=True, which makes every sync module re-download media
# it already has, so cached clips are fetched again on every poll.
# The scan interval is far longer than BlinkPy's refresh rate, so a
# normal refresh still happens on schedule.
return await self.api.refresh()
except UnauthorizedError as ex:
raise ConfigEntryAuthFailed("Blink API authorization failed") from ex
57 changes: 37 additions & 20 deletions homeassistant/components/brands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,32 +155,38 @@ async def _serve_from_custom_integration(

return None

async def _serve_from_cache_or_cdn(
async def _get_image_data(
self,
cdn_path: str,
cache_subpath: str,
*,
fallback_placeholder: bool = True,
) -> web.Response:
"""Serve from disk cache, fetching from CDN if needed."""
) -> bytes | None:
"""Read image data from the disk cache, fetching from CDN if needed."""
cache_path = self._cache_dir / cache_subpath
now = time.time()

# Try disk cache
result = await self._hass.async_add_executor_job(
_read_cached_file_with_marker, cache_path
)
if result is not None:
data, mtime = result
# Schedule background refresh if stale
if now - mtime > CACHE_TTL:
self._hass.async_create_background_task(
self._fetch_and_cache(cdn_path, cache_path),
f"brands_refresh_{cache_subpath}",
)
else:
# Cache miss - fetch from CDN
data = await self._fetch_and_cache(cdn_path, cache_path)
if result is None:
return await self._fetch_and_cache(cdn_path, cache_path)

data, mtime = result
# Schedule background refresh if stale
if time.time() - mtime > CACHE_TTL:
self._hass.async_create_background_task(
self._fetch_and_cache(cdn_path, cache_path),
f"brands_refresh_{cache_subpath}",
)
return data

async def _serve_from_cache_or_cdn(
self,
cdn_path: str,
cache_subpath: str,
*,
fallback_placeholder: bool = True,
) -> web.Response:
"""Serve from disk cache, fetching from CDN if needed."""
data = await self._get_image_data(cdn_path, cache_subpath)

if data is None:
if fallback_placeholder:
Expand Down Expand Up @@ -260,10 +266,21 @@ async def get(
) is not None:
return response

# 2. Try cache / CDN (always use direct path for proper 404 caching)
# 2. Try the integration image. Direct paths are used instead of the
# "_/" namespace so real 404s can be cached as markers.
if (
data := await self._get_image_data(
cdn_path=f"{domain}/{image}",
cache_subpath=f"integrations/{domain}/{image}",
)
) is not None:
return self._build_response(data)

# 3. Fall back to the brand image, which is cached separately so the
# integration 404 marker is preserved.
return await self._serve_from_cache_or_cdn(
cdn_path=f"brands/{domain}/{image}",
cache_subpath=f"integrations/{domain}/{image}",
cache_subpath=f"brands/{domain}/{image}",
fallback_placeholder=use_placeholder,
)

Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/collection_image/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/collection_image",
"integration_type": "service",
"iot_class": "calculated",
"quality_scale": "bronze"
"quality_scale": "silver"
}
27 changes: 25 additions & 2 deletions homeassistant/components/conversation/chat_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from homeassistant.util.json import JsonObjectType

from . import trace
from .const import ChatLogEventType
from .const import DOMAIN, ChatLogEventType
from .models import ConversationInput, ConversationResult

DATA_CHAT_LOGS: HassKey[dict[str, ChatLog]] = HassKey("conversation_chat_logs")
Expand Down Expand Up @@ -298,7 +298,20 @@ class ToolResultContent:

@property
def tool_result(self) -> JsonObjectType:
"""Return the data of the result."""
"""Return the data of the result.

Deprecated compatibility shim: the result is available as `result`.
It can be removed in HA Core 2027.11.
"""
frame.report_usage(
"accesses `ToolResultContent.tool_result`, which is deprecated; "
"use `ToolResultContent.result` instead",
breaks_in_ha_version="2027.11.0",
core_behavior=frame.ReportBehavior.ERROR,
core_integration_behavior=frame.ReportBehavior.ERROR,
custom_integration_behavior=frame.ReportBehavior.LOG,
exclude_integrations={DOMAIN},
)
return self.result.data

def as_dict(self) -> dict[str, Any]:
Expand All @@ -309,6 +322,7 @@ def as_dict(self) -> dict[str, Any]:
"tool_call_id": self.tool_call_id,
"tool_name": self.tool_name,
"result": asdict(self.result),
# Deprecated, can be removed in HA Core 2027.11.
"tool_result": self.result.data,
"created": self.created,
}
Expand Down Expand Up @@ -594,6 +608,15 @@ async def async_add_delta_content_stream(
self.delta_listener(self, filtered_delta)
elif delta["role"] == "tool_result":
if (result := delta.get("result")) is None:
frame.report_usage(
"sets `tool_result` on a tool result delta, which is "
"deprecated; set `result` to a ToolResult instead",
breaks_in_ha_version="2027.11.0",
core_behavior=frame.ReportBehavior.ERROR,
core_integration_behavior=frame.ReportBehavior.ERROR,
custom_integration_behavior=frame.ReportBehavior.LOG,
exclude_integrations={DOMAIN},
)
result = llm.ToolResult(data=delta["tool_result"])
content = ToolResultContent(
agent_id=agent_id,
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/cover/intent.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ async def async_setup_intents(hass: HomeAssistant) -> None:
SERVICE_OPEN_COVER,
description="Opens a cover",
platforms={DOMAIN},
required_domains={DOMAIN},
device_classes={CoverDeviceClass},
),
)
Expand All @@ -29,6 +30,7 @@ async def async_setup_intents(hass: HomeAssistant) -> None:
SERVICE_CLOSE_COVER,
description="Closes a cover",
platforms={DOMAIN},
required_domains={DOMAIN},
device_classes={CoverDeviceClass},
),
)
2 changes: 1 addition & 1 deletion homeassistant/components/de_dietrich/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"integration_type": "device",
"iot_class": "local_polling",
"quality_scale": "bronze",
"requirements": ["diematic-modbus==0.7.5"]
"requirements": ["diematic-modbus==0.8.0"]
}
49 changes: 16 additions & 33 deletions homeassistant/components/devolo_home_network/device_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,47 +32,30 @@ async def async_setup_entry(
str, DevoloDataUpdateCoordinator[dict[str, ConnectedStationInfo]]
] = entry.runtime_data.coordinators
registry = er.async_get(hass)
tracked = set()

@callback
def new_device_callback() -> None:
"""Add new devices if needed."""
new_entities = []
for mac_address in coordinators[CONNECTED_WIFI_CLIENTS].data:
if mac_address in tracked:
continue

new_entities.append(
DevoloScannerEntity(
coordinators[CONNECTED_WIFI_CLIENTS], device, mac_address
)
"""Add clients that don't have an entity yet."""
async_add_entities(
DevoloScannerEntity(coordinators[CONNECTED_WIFI_CLIENTS], device, mac)
for mac in coordinators[CONNECTED_WIFI_CLIENTS].data
if not registry.async_get_entity_id(
DEVICE_TRACKER_DOMAIN, DOMAIN, f"{device.serial_number}_{mac}"
)
tracked.add(mac_address)
async_add_entities(new_entities)
)

@callback
def restore_entities() -> None:
"""Restore clients that are not a part of active clients list."""
missing = []
for entity in er.async_entries_for_config_entry(registry, entry.entry_id):
if (
entity.platform == DOMAIN
and entity.domain == DEVICE_TRACKER_DOMAIN
and (
mac_address := entity.unique_id.replace(
f"{device.serial_number}_", ""
)
)
not in tracked
):
missing.append(
DevoloScannerEntity(
coordinators[CONNECTED_WIFI_CLIENTS], device, mac_address
)
)
tracked.add(mac_address)

async_add_entities(missing)
async_add_entities(
DevoloScannerEntity(
coordinators[CONNECTED_WIFI_CLIENTS],
device,
entity.unique_id.removeprefix(f"{device.serial_number}_"),
)
for entity in er.async_entries_for_config_entry(registry, entry.entry_id)
if entity.platform == DOMAIN and entity.domain == DEVICE_TRACKER_DOMAIN
)

restore_entities()
new_device_callback()
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/dlink/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

ATTRIBUTION = "Data provided by D-Link"
ATTR_TOTAL_CONSUMPTION = "total_consumption"
ATTR_CURRENT_CONSUMPTION = "current_consumption"

CONF_USE_LEGACY_PROTOCOL = "use_legacy_protocol"

Expand Down
8 changes: 7 additions & 1 deletion homeassistant/components/dlink/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from . import DLinkConfigEntry
from .const import ATTR_TOTAL_CONSUMPTION
from .const import ATTR_CURRENT_CONSUMPTION, ATTR_TOTAL_CONSUMPTION
from .entity import DLinkEntity

SCAN_INTERVAL = timedelta(minutes=2)
Expand Down Expand Up @@ -49,9 +49,15 @@ def extra_state_attributes(self) -> dict[str, Any]:
except ValueError:
total_consumption = None

try:
current_consumption = float(self.data.current_consumption)
except ValueError:
current_consumption = None

return {
ATTR_TOTAL_CONSUMPTION: total_consumption,
ATTR_TEMPERATURE: temperature,
ATTR_CURRENT_CONSUMPTION: current_consumption,
}

@property
Expand Down
4 changes: 3 additions & 1 deletion homeassistant/components/emulated_hue/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,9 @@ def _is_state_exposed(self, state: State) -> bool:
return not self._entities_with_hidden_attr_in_config[state.entity_id]

if not self.expose_by_default:
return False
# Listing an entity is the documented way to expose just a few
# devices when expose_by_default is off
return state.entity_id in self.entities
# Expose an entity if the entity's domain is exposed by default and
# the configuration doesn't explicitly exclude it from being
# exposed, or if the entity is explicitly exposed
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/environment_canada/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["env_canada"],
"requirements": ["env-canada==0.20.0"]
"requirements": ["env-canada==0.20.3"]
}
5 changes: 3 additions & 2 deletions homeassistant/components/fronius/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,10 +336,11 @@ def _modbus_mppt_descriptions(


MODBUS_INVERTER_ENTITY_DESCRIPTIONS: list[FroniusSensorEntityDescription] = [
# SunSpec model 160 supports up to 4 MPPT modules (GEN24 hybrid, Tauro)
# Verto Plus exposes 5 modules (3 PV trackers plus storage charge/discharge),
# one more leaves headroom for a hybrid with 4 PV trackers
*(
description
for mppt_no in range(1, 5)
for mppt_no in range(1, 7)
for description in _modbus_mppt_descriptions(mppt_no)
),
FroniusSensorEntityDescription(
Expand Down
4 changes: 1 addition & 3 deletions homeassistant/components/gatus/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,7 @@ rules:
entity-disabled-by-default:
status: exempt
comment: All entities represent monitored services and should be enabled by default.
entity-translations:
status: exempt
comment: Entity names are dynamically provided by the Gatus service.
entity-translations: done
exception-translations: done
icon-translations: done
reconfiguration-flow: done
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/google/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["googleapiclient"],
"requirements": ["gcal-sync==9.1.1", "oauth2client==4.1.3", "ical==14.1.1"]
"requirements": ["gcal-sync==9.1.1", "oauth2client==4.1.3", "ical==14.2.0"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/hive/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
),
BinarySensorEntityDescription(
key="GLASS_BREAK",
device_class=BinarySensorDeviceClass.SOUND,
device_class=BinarySensorDeviceClass.GLASS_BREAK,
),
)

Expand Down
13 changes: 11 additions & 2 deletions homeassistant/components/homeassistant/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import probatio

from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
from homeassistant.components.llm import LLMTools
from homeassistant.components.sensor import (
DOMAIN as SENSOR_DOMAIN,
Expand Down Expand Up @@ -45,7 +46,7 @@
DYNAMIC_CONTEXT_PROMPT = (
"You ARE equipped to answer questions about the"
" current state of\n"
"the home using the `homeassistant__GetLiveContext` tool."
"the home by retrieving live context."
" This is a primary function."
" Do not state you lack the\n"
"functionality if the question requires live data.\n"
Expand All @@ -59,7 +60,7 @@
' "What mode is the thermostat in?",'
' "What is the temperature outside?"):\n'
" 1. Recognize this requires live data.\n"
" 2. You MUST call `homeassistant__GetLiveContext`."
" 2. You MUST use the provided tool to retrieve live context."
" This tool will provide the needed real-time"
" information (like temperature from the local"
" weather, lock status, etc.).\n"
Expand Down Expand Up @@ -172,6 +173,14 @@ def async_get_exposed_entities(
if attr_name in interesting_attributes
}
):
# Tools take brightness as a 0-100 percentage; the attribute is 0-255.
if state.domain == LIGHT_DOMAIN and isinstance(
brightness := state.attributes.get("brightness"), int
):
pct = round(brightness / 255 * 100)
attributes["brightness_pct"] = str(
max(pct, 1) if brightness > 0 else pct
)
info["attributes"] = attributes

entities[state.entity_id] = info
Expand Down
Loading
Loading