Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
64e0d2e
Use LightEntityStateAttribute enum in RFXtrx (#175955)
epenet Jul 8, 2026
c680462
Use LightEntityStateAttribute enum in Pilight (#175953)
epenet Jul 8, 2026
6910388
Use FanEntityStateAttribute enum in Snooz (#175960)
epenet Jul 8, 2026
89b7a91
Use EntityStateAttribute enum in InfluxDB (#175935)
epenet Jul 8, 2026
2582f6f
Use EntityStateAttribute enum in integration (#175933)
epenet Jul 8, 2026
7e9c040
Use CoverEntityStateAttribute enum in SwitchBot (#175961)
epenet Jul 8, 2026
61e6562
Use FanEntityStateAttribute enum in Novy Cooker Hood (#175948)
epenet Jul 8, 2026
3c9ce2f
Expose Attribute enum in device_tracker and text (#175502)
epenet Jul 8, 2026
8aed1ba
Update mypy to 2.2.0 (#175924)
cdce8p Jul 8, 2026
e563c56
Migrate zone entity attributes to StrEnum (#175755)
epenet Jul 8, 2026
9c20611
Bump venstarcolortouch to 0.22 (#175867)
hplato Jul 8, 2026
050ab7a
Use UpdateEntityStateAttribute enum in Matter (#175945)
epenet Jul 8, 2026
28003fe
Use state attribute enums in Modbus (#175946)
epenet Jul 8, 2026
0d538df
Use ZoneEntityStateAttribute enum in Open-Meteo (#175978)
epenet Jul 8, 2026
e05ae4e
Rename component to integration in Etherscan (#175535)
smarthome-10 Jul 8, 2026
1020c06
Remove previously deprecated battery props from SwitchBot Cloud (#175…
gjohansson-ST Jul 8, 2026
0232f40
Add assist_satellite LLM tools platform (#175655)
balloob Jul 8, 2026
fbbbc18
Replace MonarchMoney Integration Dependency to MonarchMoneyCommunity …
bradleyseanf Jul 8, 2026
3bd3722
MELCloud add text selectors (#175131)
erwindouna Jul 8, 2026
f00dfc7
Add Luci openwrt scanner config flow (#167308)
kristbaum Jul 8, 2026
079be5d
Fix Teslemetry steering-wheel-heat select IndexError on out-of-range …
Bre77 Jul 8, 2026
204bab7
Rename netatmo data_handler to coordinator (#175868)
cgtobi Jul 8, 2026
4c9189e
Use state attribute enums in Tesla Fleet (#175969)
epenet Jul 8, 2026
acdcc83
Use state attribute enums in temperature (#175967)
epenet Jul 8, 2026
ba9445d
Use ClimateEntityStateAttribute enum in SwitchBot Cloud (#175962)
epenet Jul 8, 2026
5b0ac67
Use ClimateEntityStateAttribute enum in ScreenLogic (#175959)
epenet Jul 8, 2026
2be826b
Use EntityStateAttribute enum in statistics (#175957)
epenet Jul 8, 2026
8956f02
Use ScheduleEntityStateAttribute enum in schedule triggers (#175958)
epenet Jul 8, 2026
e7b5065
Use EntityStateAttribute enum in Space API (#175956)
epenet Jul 8, 2026
193550b
Use LightEntityStateAttribute enum in RFLink (#175954)
epenet Jul 8, 2026
7eca76f
Use state attribute enums in Prometheus (#175952)
epenet Jul 8, 2026
8e59898
Show proper error message if Google Health API is disabled (#175994)
joostlek Jul 8, 2026
5f29fa6
Use LockEntityStateAttribute enum in Yale (#175990)
epenet Jul 8, 2026
d33997d
Fix Teslemetry streaming update progress tracking and state restore (…
Bre77 Jul 8, 2026
be8acc3
Use EntityStateAttribute enum in Tomorrow.io (#175974)
epenet Jul 8, 2026
3cd3082
Refactor bsblan schedule service helpers (#172340)
liudger Jul 8, 2026
a58d55e
MELCloud Home add energy consumption (#173948)
erwindouna Jul 8, 2026
642b079
Refactor SupervisorJobs into a DataUpdateCoordinator (#174237)
mdegat01 Jul 8, 2026
bd575bb
Use UpdateEntityStateAttribute enum in Shelly (#175995)
epenet Jul 8, 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
5 changes: 3 additions & 2 deletions CODEOWNERS

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

2 changes: 1 addition & 1 deletion homeassistant/components/apple_tv/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ def state(self) -> MediaPlayerState | None:
return MediaPlayerState.PLAYING
if state in (DeviceState.Paused, DeviceState.Seeking, DeviceState.Stopped):
return MediaPlayerState.PAUSED
return MediaPlayerState.IDLE # Bad or unknown state?
return MediaPlayerState.IDLE # type: ignore[unreachable] # Bad or unknown state?
return None

@callback
Expand Down
24 changes: 24 additions & 0 deletions homeassistant/components/assist_satellite/llm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""LLM tools for the assist_satellite integration."""

from homeassistant.components.llm import LLMTools
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import intent
from homeassistant.helpers.llm import LLM_API_ASSIST, IntentTool, LLMContext, Tool


@callback
def async_get_tools(
hass: HomeAssistant, llm_context: LLMContext, api_id: str
) -> LLMTools | None:
"""Return the broadcast LLM tool."""
if api_id != LLM_API_ASSIST:
return None

# assist_satellite registers the broadcast intent when it is set up, and
# this platform is only queried once that has happened.
tools: list[Tool] = [
IntentTool(handler.intent_type, handler)
for handler in intent.async_get(hass)
if handler.intent_type == intent.INTENT_BROADCAST
]
return LLMTools(tools=tools)
145 changes: 50 additions & 95 deletions homeassistant/components/bsblan/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from datetime import time
import logging
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any, Final

from bsblan import BSBLANError, DaySchedule, DHWSchedule, TimeSlot
import voluptuous as vol
Expand All @@ -29,6 +29,16 @@
ATTR_SATURDAY_SLOTS = "saturday_slots"
ATTR_SUNDAY_SLOTS = "sunday_slots"

_DAY_NAME_SLOT_ATTR_PAIRS: tuple[tuple[str, str], ...] = (
("monday", ATTR_MONDAY_SLOTS),
("tuesday", ATTR_TUESDAY_SLOTS),
("wednesday", ATTR_WEDNESDAY_SLOTS),
("thursday", ATTR_THURSDAY_SLOTS),
("friday", ATTR_FRIDAY_SLOTS),
("saturday", ATTR_SATURDAY_SLOTS),
("sunday", ATTR_SUNDAY_SLOTS),
)


# Schema for a single time slot
_SLOT_SCHEMA = vol.Schema(
Expand All @@ -39,16 +49,16 @@
)


_WEEKLY_SCHEDULE_FIELDS: Final[dict[vol.Marker, Any]] = {
vol.Optional(slot_attr): vol.All(cv.ensure_list, [_SLOT_SCHEMA])
for _, slot_attr in _DAY_NAME_SLOT_ATTR_PAIRS
}


SERVICE_SET_HOT_WATER_SCHEDULE_SCHEMA = vol.Schema(
{
vol.Required(ATTR_DEVICE_ID): cv.string,
vol.Optional(ATTR_MONDAY_SLOTS): vol.All(cv.ensure_list, [_SLOT_SCHEMA]),
vol.Optional(ATTR_TUESDAY_SLOTS): vol.All(cv.ensure_list, [_SLOT_SCHEMA]),
vol.Optional(ATTR_WEDNESDAY_SLOTS): vol.All(cv.ensure_list, [_SLOT_SCHEMA]),
vol.Optional(ATTR_THURSDAY_SLOTS): vol.All(cv.ensure_list, [_SLOT_SCHEMA]),
vol.Optional(ATTR_FRIDAY_SLOTS): vol.All(cv.ensure_list, [_SLOT_SCHEMA]),
vol.Optional(ATTR_SATURDAY_SLOTS): vol.All(cv.ensure_list, [_SLOT_SCHEMA]),
vol.Optional(ATTR_SUNDAY_SLOTS): vol.All(cv.ensure_list, [_SLOT_SCHEMA]),
**_WEEKLY_SCHEDULE_FIELDS,
}
)

Expand Down Expand Up @@ -98,11 +108,26 @@ def _convert_time_slots_to_day_schedule(
return DaySchedule(slots=time_slots)


async def set_hot_water_schedule(service_call: ServiceCall) -> None:
"""Set hot water heating schedule."""
device_id = service_call.data[ATTR_DEVICE_ID]
def _build_weekly_schedule_days(
service_call: ServiceCall,
) -> dict[str, DaySchedule | None]:
"""Build day-name -> schedule values from the service call data.

Days omitted from the service call map to None, which tells python-bsblan not to
modify that day.
"""
return {
day_name: _convert_time_slots_to_day_schedule(service_call.data.get(attr_name))
for day_name, attr_name in _DAY_NAME_SLOT_ATTR_PAIRS
}


def _resolve_config_entry(
service_call: ServiceCall,
) -> tuple[BSBLanConfigEntry, dr.DeviceEntry]:
"""Resolve device_id from a service call into a loaded BSBLAN config entry."""
device_id: str = service_call.data[ATTR_DEVICE_ID]

# Get the device and config entry
device_registry = dr.async_get(service_call.hass)
device_entry = device_registry.async_get(device_id)

Expand Down Expand Up @@ -137,56 +162,20 @@ async def set_hot_water_schedule(service_call: ServiceCall) -> None:
translation_placeholders={"device_name": device_entry.name or device_id},
)

client = entry.runtime_data.client
return entry, device_entry

# Convert time slots to DaySchedule objects
monday = _convert_time_slots_to_day_schedule(
service_call.data.get(ATTR_MONDAY_SLOTS)
)
tuesday = _convert_time_slots_to_day_schedule(
service_call.data.get(ATTR_TUESDAY_SLOTS)
)
wednesday = _convert_time_slots_to_day_schedule(
service_call.data.get(ATTR_WEDNESDAY_SLOTS)
)
thursday = _convert_time_slots_to_day_schedule(
service_call.data.get(ATTR_THURSDAY_SLOTS)
)
friday = _convert_time_slots_to_day_schedule(
service_call.data.get(ATTR_FRIDAY_SLOTS)
)
saturday = _convert_time_slots_to_day_schedule(
service_call.data.get(ATTR_SATURDAY_SLOTS)
)
sunday = _convert_time_slots_to_day_schedule(
service_call.data.get(ATTR_SUNDAY_SLOTS)
)

# Create the DHWSchedule object
dhw_schedule = DHWSchedule(
monday=monday,
tuesday=tuesday,
wednesday=wednesday,
thursday=thursday,
friday=friday,
saturday=saturday,
sunday=sunday,
)
async def set_hot_water_schedule(service_call: ServiceCall) -> None:
"""Set hot water heating schedule."""
entry, _ = _resolve_config_entry(service_call)
client = entry.runtime_data.client

LOGGER.debug(
"Setting hot water schedule - Monday: %s, Tuesday: %s, Wednesday: %s, "
"Thursday: %s, Friday: %s, Saturday: %s, Sunday: %s",
monday,
tuesday,
wednesday,
thursday,
friday,
saturday,
sunday,
)
days = _build_weekly_schedule_days(service_call)
dhw_schedule = DHWSchedule(**days)

LOGGER.debug("Setting hot water schedule: %s", dhw_schedule)

try:
# Call the BSB-LAN API to set the schedule
await client.set_hot_water_schedule(dhw_schedule)
except BSBLANError as err:
raise HomeAssistantError(
Expand All @@ -201,45 +190,11 @@ async def set_hot_water_schedule(service_call: ServiceCall) -> None:

async def async_sync_time(service_call: ServiceCall) -> None:
"""Synchronize BSB-LAN device time with Home Assistant."""
device_id: str = service_call.data[ATTR_DEVICE_ID]

# Get the device and config entry
device_registry = dr.async_get(service_call.hass)
device_entry = device_registry.async_get(device_id)

if device_entry is None:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="invalid_device_id",
translation_placeholders={"device_id": device_id},
)

# Find the config entry for this device
matching_entries: list[BSBLanConfigEntry] = [
entry
for entry in service_call.hass.config_entries.async_entries(DOMAIN)
if entry.entry_id in device_entry.config_entries
]

if not matching_entries:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="no_config_entry_for_device",
translation_placeholders={"device_id": device_entry.name or device_id},
)

entry = matching_entries[0]

# Verify the config entry is loaded
if entry.state is not ConfigEntryState.LOADED:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="config_entry_not_loaded",
translation_placeholders={"device_name": device_entry.name or device_id},
)

entry, device_entry = _resolve_config_entry(service_call)
client = entry.runtime_data.client
await async_sync_device_time(client, device_entry.name or device_id)
await async_sync_device_time(
client, device_entry.name or service_call.data[ATTR_DEVICE_ID]
)


SYNC_TIME_SCHEMA = vol.Schema(
Expand Down
3 changes: 1 addition & 2 deletions homeassistant/components/conversation/trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,7 @@ async def call_action(
if isinstance(
automation_result, ScriptRunResult
) and automation_result.conversation_response not in (None, UNDEFINED):
# mypy does not understand the type narrowing, unclear why
return automation_result.conversation_response # type: ignore[return-value]
return automation_result.conversation_response

# It's important to return None here instead of a string.
#
Expand Down
4 changes: 4 additions & 0 deletions homeassistant/components/device_tracker/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@
LOGGER,
PLATFORM_TYPE_LEGACY,
SCAN_INTERVAL,
DeviceTrackerEntityCapabilityAttribute,
DeviceTrackerEntityStateAttribute,
ScannerEntityStateAttribute,
SourceType,
TrackerEntityStateAttribute,
TrackingType,
)
from .entity import ( # noqa: F401
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/etherscan/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
"""The etherscan component."""
"""The Etherscan integration."""
13 changes: 11 additions & 2 deletions homeassistant/components/google_health/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@

from google_health_api import GoogleHealthApi
from google_health_api.const import HealthApiScope
from google_health_api.exceptions import GoogleHealthApiError
from google_health_api.exceptions import (
GoogleHealthApiError,
HealthApiForbiddenException,
)

from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlowResult
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_TOKEN
from homeassistant.helpers import aiohttp_client, config_entry_oauth2_flow

from .api import SimpleAuth
from .const import DEFAULT_TITLE, DOMAIN, OAUTH_SCOPES
from .const import API_CONSOLE_URL, DEFAULT_TITLE, DOMAIN, OAUTH_SCOPES

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -68,6 +71,12 @@ async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResu

try:
identity = await api.get_identity()
except HealthApiForbiddenException as err:
_LOGGER.error("Error getting Google Health identity: %s", err)
return self.async_abort(
reason="api_not_enabled",
description_placeholders={"url": API_CONSOLE_URL},
)
except GoogleHealthApiError as err:
_LOGGER.error("Error getting Google Health identity: %s", err)
return self.async_abort(reason="cannot_connect")
Expand Down
4 changes: 4 additions & 0 deletions homeassistant/components/google_health/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
OAUTH2_AUTHORIZE = "https://accounts.google.com/o/oauth2/v2/auth"
OAUTH2_TOKEN = "https://oauth2.googleapis.com/token"

API_CONSOLE_URL = (
"https://console.developers.google.com/apis/api/health.googleapis.com/overview"
)

DEFAULT_TITLE = "Google Health"

OAUTH_SCOPES = [
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/google_health/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"api_not_enabled": "The Google Health API is not enabled for your Google Cloud project. Enable it in the [Google Cloud Console]({url}), wait a few minutes for the change to propagate, then try again.",
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
"cannot_connect": "Failed to connect.",
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
Expand Down
11 changes: 8 additions & 3 deletions homeassistant/components/hassio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,15 @@
DATA_HASSIO_SUPERVISOR_USER,
DATA_KEY_SUPERVISOR_ISSUES,
DOMAIN,
JOBS_COORDINATOR,
MAIN_COORDINATOR,
STATS_COORDINATOR,
)
from .coordinator import (
HassioAddOnDataUpdateCoordinator,
HassioMainDataUpdateCoordinator,
HassioStatsDataUpdateCoordinator,
SupervisorJobsCoordinator,
get_addons_info,
get_addons_list,
get_addons_stats,
Expand Down Expand Up @@ -326,9 +328,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await coordinator.async_config_entry_first_refresh()
hass.data[MAIN_COORDINATOR] = coordinator

addon_coordinator = HassioAddOnDataUpdateCoordinator(
hass, entry, dev_reg, coordinator.jobs
)
jobs_coordinator = SupervisorJobsCoordinator(hass, entry)
await jobs_coordinator.async_config_entry_first_refresh()
hass.data[JOBS_COORDINATOR] = jobs_coordinator

addon_coordinator = HassioAddOnDataUpdateCoordinator(hass, entry, dev_reg)
await addon_coordinator.async_config_entry_first_refresh()
hass.data[ADDONS_COORDINATOR] = addon_coordinator

Expand Down Expand Up @@ -437,5 +441,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data.pop(MAIN_COORDINATOR, None)
hass.data.pop(ADDONS_COORDINATOR, None)
hass.data.pop(STATS_COORDINATOR, None)
hass.data.pop(JOBS_COORDINATOR, None)

return unload_ok
5 changes: 5 additions & 0 deletions homeassistant/components/hassio/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
HassioAddOnDataUpdateCoordinator,
HassioMainDataUpdateCoordinator,
HassioStatsDataUpdateCoordinator,
SupervisorJobsCoordinator,
)
from .handler import HassIO
from .issues import SupervisorIssues
Expand Down Expand Up @@ -103,6 +104,9 @@
STATS_COORDINATOR: HassKey[HassioStatsDataUpdateCoordinator] = HassKey(
"hassio_stats_coordinator"
)
JOBS_COORDINATOR: HassKey[SupervisorJobsCoordinator] = HassKey(
"hassio_jobs_coordinator"
)


DATA_COMPONENT: HassKey[HassIO] = HassKey(DOMAIN)
Expand All @@ -126,6 +130,7 @@
HASSIO_MAIN_UPDATE_INTERVAL = timedelta(minutes=5)
HASSIO_ADDON_UPDATE_INTERVAL = timedelta(minutes=15)
HASSIO_STATS_UPDATE_INTERVAL = timedelta(seconds=60)
SUPERVISOR_JOBS_UPDATE_INTERVAL = timedelta(minutes=15)

ATTR_AUTO_UPDATE = "auto_update"
ATTR_VERSION = "version"
Expand Down
Loading
Loading