Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
2ec5190
Install requirements_test_all in dev (#157392)
balloob Nov 27, 2025
7e8496a
Bump hass-nabucasa from 1.6.1 to 1.6.2 (#157405)
victorigualada Nov 27, 2025
7fe2622
Bump renault-api to 0.5.1 (#157411)
epenet Nov 27, 2025
3a65d3c
Add tests to Transmission (#157355)
andrew-codechimp Nov 27, 2025
32fe53c
Add anthropic model to the device info (#157413)
Shulyaka Nov 27, 2025
7711eac
Return early when setting cloud ai_task and conversation and not logg…
victorigualada Nov 27, 2025
988355e
Add tests for the switch platform to the AdGuard Home integration (#1…
mib1185 Nov 27, 2025
dbcde54
Update Shelly coordinator coverage to 100% (#157380)
thecode Nov 27, 2025
ec69efe
Fix parsing of Tuya electricity RAW values (#157039)
abelyliu Nov 27, 2025
7a32853
Bugfix: Essent remove average gas price today (#157317)
jaapp Nov 27, 2025
8fe79a8
Fix state classes of Ecowitt rain sensors (#157409)
sairon Nov 27, 2025
d2248d2
Default conversation agent to store tool calls in chat log (#157377)
balloob Nov 27, 2025
2bb51e1
Reduce Devcontainer docker layers (#157412)
edenhaus Nov 27, 2025
0769163
Use "medium" instead of "med" for the medium fan mode in Coolmaster (…
tan-lawrence Nov 27, 2025
0b2bb9f
Modernize template binary sensor (#157279)
Petro31 Nov 27, 2025
a7dbf55
Add climate started_cooling and started_drying triggers (#156945)
emontnemery Nov 27, 2025
83c4e2a
Fix Anthropic init with incorrect model (#157421)
Shulyaka Nov 27, 2025
9ee7ed5
Fix MAC address mix-ups between WLED devices (#155491)
mik-laj Nov 27, 2025
f7c41e6
Add media content id attribute to Bang & Olufsen (#156597)
mj23000 Nov 27, 2025
d045eca
Add parallel_updates to SFR Box (#157426)
epenet Nov 27, 2025
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
19 changes: 8 additions & 11 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -35,25 +35,22 @@ COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv

USER vscode

COPY .python-version ./
RUN uv python install

ENV VIRTUAL_ENV="/home/vscode/.local/ha-venv"
RUN uv venv $VIRTUAL_ENV
RUN --mount=type=bind,source=.python-version,target=.python-version \
uv python install \
&& uv venv $VIRTUAL_ENV
ENV PATH="$VIRTUAL_ENV/bin:$PATH"

WORKDIR /tmp

# Setup hass-release
RUN git clone --depth 1 https://github.com/home-assistant/hass-release ~/hass-release \
&& uv pip install -e ~/hass-release/

# Install Python dependencies from requirements
COPY requirements.txt ./
COPY homeassistant/package_constraints.txt homeassistant/package_constraints.txt
RUN uv pip install -r requirements.txt
COPY requirements_test.txt requirements_test_pre_commit.txt ./
RUN uv pip install -r requirements_test.txt
RUN --mount=type=bind,source=requirements.txt,target=requirements.txt \
--mount=type=bind,source=homeassistant/package_constraints.txt,target=homeassistant/package_constraints.txt \
--mount=type=bind,source=requirements_test.txt,target=requirements_test.txt \
--mount=type=bind,source=requirements_test_pre_commit.txt,target=requirements_test_pre_commit.txt \
uv pip install -r requirements.txt -r requirements_test.txt

WORKDIR /workspaces

Expand Down
11 changes: 2 additions & 9 deletions homeassistant/components/anthropic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
)
from homeassistant.helpers.typing import ConfigType

from .const import CONF_CHAT_MODEL, DEFAULT, DEFAULT_CONVERSATION_NAME, DOMAIN, LOGGER
from .const import DEFAULT_CONVERSATION_NAME, DOMAIN, LOGGER

PLATFORMS = (Platform.AI_TASK, Platform.CONVERSATION)
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
Expand All @@ -37,14 +37,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: AnthropicConfigEntry) ->
partial(anthropic.AsyncAnthropic, api_key=entry.data[CONF_API_KEY])
)
try:
# Use model from first conversation subentry for validation
subentries = list(entry.subentries.values())
if subentries:
model_id = subentries[0].data.get(CONF_CHAT_MODEL, DEFAULT[CONF_CHAT_MODEL])
else:
model_id = DEFAULT[CONF_CHAT_MODEL]
model = await client.models.retrieve(model_id=model_id, timeout=10.0)
LOGGER.debug("Anthropic model: %s", model.display_name)
await client.models.list(timeout=10.0)
except anthropic.AuthenticationError as err:
LOGGER.error("Invalid API key: %s", err)
return False
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/anthropic/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ def __init__(self, entry: AnthropicConfigEntry, subentry: ConfigSubentry) -> Non
identifiers={(DOMAIN, subentry.subentry_id)},
name=subentry.title,
manufacturer="Anthropic",
model="Claude",
model=subentry.data.get(CONF_CHAT_MODEL, DEFAULT[CONF_CHAT_MODEL]),
entry_type=dr.DeviceEntryType.SERVICE,
)

Expand Down
14 changes: 14 additions & 0 deletions homeassistant/components/bang_olufsen/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@
class BangOlufsenSource:
"""Class used for associating device source ids with friendly names. May not include all sources."""

DEEZER: Final[Source] = Source(name="Deezer", id="deezer")
LINE_IN: Final[Source] = Source(name="Line-In", id="lineIn")
NET_RADIO: Final[Source] = Source(name="B&O Radio", id="netRadio")
SPDIF: Final[Source] = Source(name="Optical", id="spdif")
TIDAL: Final[Source] = Source(name="Tidal", id="tidal")
UNKNOWN: Final[Source] = Source(name="Unknown Source", id="unknown")
URI_STREAMER: Final[Source] = Source(name="Audio Streamer", id="uriStreamer")


Expand Down Expand Up @@ -78,6 +82,16 @@ class BangOlufsenModel(StrEnum):
BEOREMOTE_ONE = "Beoremote One"


class BangOlufsenAttribute(StrEnum):
"""Enum for extra_state_attribute keys."""

BEOLINK = "beolink"
BEOLINK_PEERS = "peers"
BEOLINK_SELF = "self"
BEOLINK_LEADER = "leader"
BEOLINK_LISTENERS = "listeners"


# Physical "buttons" on devices
class BangOlufsenButtons(StrEnum):
"""Enum for device buttons."""
Expand Down
55 changes: 40 additions & 15 deletions homeassistant/components/bang_olufsen/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
FALLBACK_SOURCES,
MANUFACTURER,
VALID_MEDIA_TYPES,
BangOlufsenAttribute,
BangOlufsenMediaType,
BangOlufsenSource,
WebsocketNotification,
Expand Down Expand Up @@ -224,7 +225,8 @@ def __init__(self, entry: ConfigEntry, client: MozartClient) -> None:
# Beolink compatible sources
self._beolink_sources: dict[str, bool] = {}
self._remote_leader: BeolinkLeader | None = None
# Extra state attributes for showing Beolink: peer(s), listener(s), leader and self
# Extra state attributes:
# Beolink: peer(s), listener(s), leader and self
self._beolink_attributes: dict[str, dict[str, dict[str, str]]] = {}

async def async_added_to_hass(self) -> None:
Expand Down Expand Up @@ -436,7 +438,10 @@ async def _async_update_name_and_beolink(self) -> None:
await self._async_update_beolink()

async def _async_update_beolink(self) -> None:
"""Update the current Beolink leader, listeners, peers and self."""
"""Update the current Beolink leader, listeners, peers and self.

Updates Home Assistant state.
"""

self._beolink_attributes = {}

Expand All @@ -445,18 +450,24 @@ async def _async_update_beolink(self) -> None:

# Add Beolink self
self._beolink_attributes = {
"beolink": {"self": {self.device_entry.name: self._beolink_jid}}
BangOlufsenAttribute.BEOLINK: {
BangOlufsenAttribute.BEOLINK_SELF: {
self.device_entry.name: self._beolink_jid
}
}
}

# Add Beolink peers
peers = await self._client.get_beolink_peers()

if len(peers) > 0:
self._beolink_attributes["beolink"]["peers"] = {}
self._beolink_attributes[BangOlufsenAttribute.BEOLINK][
BangOlufsenAttribute.BEOLINK_PEERS
] = {}
for peer in peers:
self._beolink_attributes["beolink"]["peers"][peer.friendly_name] = (
peer.jid
)
self._beolink_attributes[BangOlufsenAttribute.BEOLINK][
BangOlufsenAttribute.BEOLINK_PEERS
][peer.friendly_name] = peer.jid

# Add Beolink listeners / leader
self._remote_leader = self._playback_metadata.remote_leader
Expand All @@ -477,7 +488,9 @@ async def _async_update_beolink(self) -> None:
# Add self
group_members.append(self.entity_id)

self._beolink_attributes["beolink"]["leader"] = {
self._beolink_attributes[BangOlufsenAttribute.BEOLINK][
BangOlufsenAttribute.BEOLINK_LEADER
] = {
self._remote_leader.friendly_name: self._remote_leader.jid,
}

Expand Down Expand Up @@ -514,9 +527,9 @@ async def _async_update_beolink(self) -> None:
beolink_listener.jid
)
break
self._beolink_attributes["beolink"]["listeners"] = (
beolink_listeners_attribute
)
self._beolink_attributes[BangOlufsenAttribute.BEOLINK][
BangOlufsenAttribute.BEOLINK_LISTENERS
] = beolink_listeners_attribute

self._attr_group_members = group_members

Expand Down Expand Up @@ -615,11 +628,18 @@ def is_volume_muted(self) -> bool | None:
return None

@property
def media_content_type(self) -> str:
def media_content_type(self) -> MediaType | str | None:
"""Return the current media type."""
# Hard to determine content type
if self._source_change.id == BangOlufsenSource.URI_STREAMER.id:
return MediaType.URL
content_type = {
BangOlufsenSource.URI_STREAMER.id: MediaType.URL,
BangOlufsenSource.DEEZER.id: BangOlufsenMediaType.DEEZER,
BangOlufsenSource.TIDAL.id: BangOlufsenMediaType.TIDAL,
BangOlufsenSource.NET_RADIO.id: BangOlufsenMediaType.RADIO,
}
# Hard to determine content type.
if self._source_change.id in content_type:
return content_type[self._source_change.id]

return MediaType.MUSIC

@property
Expand All @@ -632,6 +652,11 @@ def media_position(self) -> int | None:
"""Return the current playback progress."""
return self._playback_progress.progress

@property
def media_content_id(self) -> str | None:
"""Return internal ID of Deezer, Tidal and radio stations."""
return self._playback_metadata.source_internal_id

@property
def media_image_url(self) -> str | None:
"""Return URL of the currently playing music."""
Expand Down
6 changes: 6 additions & 0 deletions homeassistant/components/climate/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@
}
},
"triggers": {
"started_cooling": {
"trigger": "mdi:snowflake"
},
"started_drying": {
"trigger": "mdi:water-percent"
},
"started_heating": {
"trigger": "mdi:fire"
},
Expand Down
22 changes: 22 additions & 0 deletions homeassistant/components/climate/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,28 @@
},
"title": "Climate",
"triggers": {
"started_cooling": {
"description": "Triggers when a climate started cooling.",
"description_configured": "[%key:component::climate::triggers::started_cooling::description%]",
"fields": {
"behavior": {
"description": "[%key:component::climate::common::trigger_behavior_description%]",
"name": "[%key:component::climate::common::trigger_behavior_name%]"
}
},
"name": "When a climate started cooling"
},
"started_drying": {
"description": "Triggers when a climate started drying.",
"description_configured": "[%key:component::climate::triggers::started_drying::description%]",
"fields": {
"behavior": {
"description": "[%key:component::climate::common::trigger_behavior_description%]",
"name": "[%key:component::climate::common::trigger_behavior_name%]"
}
},
"name": "When a climate started drying"
},
"started_heating": {
"description": "Triggers when a climate starts to heat.",
"description_configured": "[%key:component::climate::triggers::started_heating::description%]",
Expand Down
6 changes: 6 additions & 0 deletions homeassistant/components/climate/trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
from .const import ATTR_HVAC_ACTION, DOMAIN, HVACAction, HVACMode

TRIGGERS: dict[str, type[Trigger]] = {
"started_cooling": make_entity_state_attribute_trigger(
DOMAIN, ATTR_HVAC_ACTION, HVACAction.COOLING
),
"started_drying": make_entity_state_attribute_trigger(
DOMAIN, ATTR_HVAC_ACTION, HVACAction.DRYING
),
"turned_off": make_entity_state_trigger(DOMAIN, HVACMode.OFF),
"turned_on": make_conditional_entity_state_trigger(
DOMAIN,
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/climate/triggers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
- last
- any

started_cooling: *trigger_common
started_drying: *trigger_common
started_heating: *trigger_common
turned_off: *trigger_common
turned_on: *trigger_common
6 changes: 4 additions & 2 deletions homeassistant/components/cloud/ai_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from json import JSONDecodeError
import logging

from hass_nabucasa import NabuCasaBaseError
from hass_nabucasa.llm import (
LLMAuthenticationError,
LLMError,
Expand Down Expand Up @@ -93,10 +94,11 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Home Assistant Cloud AI Task entity."""
cloud = hass.data[DATA_CLOUD]
if not (cloud := hass.data[DATA_CLOUD]).is_logged_in:
return
try:
await cloud.llm.async_ensure_token()
except LLMError:
except (LLMError, NabuCasaBaseError):
return

async_add_entities([CloudLLMTaskEntity(cloud, config_entry)])
Expand Down
6 changes: 4 additions & 2 deletions homeassistant/components/cloud/conversation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from typing import Literal

from hass_nabucasa import NabuCasaBaseError
from hass_nabucasa.llm import LLMError

from homeassistant.components import conversation
Expand All @@ -23,10 +24,11 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Home Assistant Cloud conversation entity."""
cloud = hass.data[DATA_CLOUD]
if not (cloud := hass.data[DATA_CLOUD]).is_logged_in:
return
try:
await cloud.llm.async_ensure_token()
except LLMError:
except (LLMError, NabuCasaBaseError):
return

async_add_entities([CloudConversationEntity(cloud, config_entry)])
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/cloud/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
"integration_type": "system",
"iot_class": "cloud_push",
"loggers": ["acme", "hass_nabucasa", "snitun"],
"requirements": ["hass-nabucasa==1.6.1"],
"requirements": ["hass-nabucasa==1.6.2"],
"single_config_entry": true
}
Loading
Loading