Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions homeassistant/components/asuswrt/bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,19 @@ def get_bridge(

def __init__(self, host: str) -> None:
"""Initialize Bridge."""
self._configuration_url = f"http://{host}"
self._host = host
self._firmware: str | None = None
self._label_mac: str | None = None
self._model: str | None = None
self._model_id: str | None = None
self._serial_number: str | None = None

@property
def configuration_url(self) -> str:
"""Return configuration URL."""
return self._configuration_url

@property
def host(self) -> str:
"""Return hostname."""
Expand Down Expand Up @@ -371,6 +377,7 @@ async def async_connect(self) -> None:
# get main router properties
if mac := _identity.mac:
self._label_mac = format_mac(mac)
self._configuration_url = self._api.webpanel
self._firmware = str(_identity.firmware)
self._model = _identity.model
self._model_id = _identity.product_id
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/asuswrt/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,13 +388,13 @@ def update_options(self, new_options: Mapping[str, Any]) -> bool:
def device_info(self) -> DeviceInfo:
"""Return the device information."""
info = DeviceInfo(
configuration_url=self._api.configuration_url,
identifiers={(DOMAIN, self._entry.unique_id or "AsusWRT")},
name=self.host,
model=self._api.model or "Asus Router",
model_id=self._api.model_id,
serial_number=self._api.serial_number,
manufacturer="Asus",
configuration_url=f"http://{self.host}",
)
if self._api.firmware:
info["sw_version"] = self._api.firmware
Expand Down
23 changes: 23 additions & 0 deletions homeassistant/components/derivative/diagnostics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Diagnostics support for derivative."""

from __future__ import annotations

from typing import Any

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er


async def async_get_config_entry_diagnostics(
hass: HomeAssistant, config_entry: ConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""

registry = er.async_get(hass)
entities = registry.entities.get_entries_for_config_entry_id(config_entry.entry_id)

return {
"config_entry": config_entry.as_dict(),
"entity": [entity.extended_dict for entity in entities],
}
11 changes: 11 additions & 0 deletions homeassistant/components/ecowitt/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,17 @@
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
EcoWittSensorTypes.DISTANCE_MM: SensorEntityDescription(
key="DISTANCE_MM",
device_class=SensorDeviceClass.DISTANCE,
native_unit_of_measurement=UnitOfLength.MILLIMETERS,
state_class=SensorStateClass.MEASUREMENT,
),
EcoWittSensorTypes.HEAT_COUNT: SensorEntityDescription(
key="HEAT_COUNT",
state_class=SensorStateClass.TOTAL_INCREASING,
entity_category=EntityCategory.DIAGNOSTIC,
),
EcoWittSensorTypes.PM1: SensorEntityDescription(
key="PM1",
device_class=SensorDeviceClass.PM1,
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/emoncms/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/emoncms",
"iot_class": "local_polling",
"requirements": ["pyemoncms==0.1.2"]
"requirements": ["pyemoncms==0.1.3"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/emoncms_history/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/emoncms_history",
"iot_class": "local_polling",
"quality_scale": "legacy",
"requirements": ["pyemoncms==0.1.2"]
"requirements": ["pyemoncms==0.1.3"]
}
4 changes: 2 additions & 2 deletions homeassistant/components/hassio/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@
},
"unsupported_docker_version": {
"title": "Unsupported system - Docker version",
"description": "System is unsupported because the wrong version of Docker is in use. Use the link to learn the correct version and how to fix this."
"description": "System is unsupported because the Docker version is out of date. For information about the required version and how to fix this, select Learn more."
},
"unsupported_job_conditions": {
"title": "Unsupported system - Protections disabled",
Expand All @@ -209,7 +209,7 @@
},
"unsupported_os": {
"title": "Unsupported system - Operating System",
"description": "System is unsupported because the operating system in use is not tested or maintained for use with Supervisor. Use the link to which operating systems are supported and how to fix this."
"description": "System is unsupported because the operating system in use is not tested or maintained for use with Supervisor. For information about supported operating systems and how to fix this, select Learn more."
},
"unsupported_os_agent": {
"title": "Unsupported system - OS-Agent issues",
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/imgw_pib/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/imgw_pib",
"iot_class": "cloud_polling",
"quality_scale": "silver",
"requirements": ["imgw_pib==1.5.4"]
"requirements": ["imgw_pib==1.5.6"]
}
22 changes: 13 additions & 9 deletions homeassistant/components/knx/storage/entity_store_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,27 +118,31 @@
vol.Schema(
{
"section_binary_control": KNXSectionFlat(),
vol.Optional(CONF_GA_UP_DOWN): GASelector(state=False),
vol.Optional(CONF_GA_UP_DOWN): GASelector(state=False, valid_dpt="1"),
vol.Optional(CoverConf.INVERT_UPDOWN): selector.BooleanSelector(),
"section_stop_control": KNXSectionFlat(),
vol.Optional(CONF_GA_STOP): GASelector(state=False),
vol.Optional(CONF_GA_STEP): GASelector(state=False),
vol.Optional(CONF_GA_STOP): GASelector(state=False, valid_dpt="1"),
vol.Optional(CONF_GA_STEP): GASelector(state=False, valid_dpt="1"),
"section_position_control": KNXSectionFlat(collapsible=True),
vol.Optional(CONF_GA_POSITION_SET): GASelector(state=False),
vol.Optional(CONF_GA_POSITION_STATE): GASelector(write=False),
vol.Optional(CONF_GA_POSITION_SET): GASelector(
state=False, valid_dpt="5.001"
),
vol.Optional(CONF_GA_POSITION_STATE): GASelector(
write=False, valid_dpt="5.001"
),
vol.Optional(CoverConf.INVERT_POSITION): selector.BooleanSelector(),
"section_tilt_control": KNXSectionFlat(collapsible=True),
vol.Optional(CONF_GA_ANGLE): GASelector(),
vol.Optional(CONF_GA_ANGLE): GASelector(valid_dpt="5.001"),
vol.Optional(CoverConf.INVERT_ANGLE): selector.BooleanSelector(),
"section_travel_time": KNXSectionFlat(),
vol.Optional(
vol.Required(
CoverConf.TRAVELLING_TIME_UP, default=25
): selector.NumberSelector(
selector.NumberSelectorConfig(
min=0, max=1000, step=0.1, unit_of_measurement="s"
)
),
vol.Optional(
vol.Required(
CoverConf.TRAVELLING_TIME_DOWN, default=25
): selector.NumberSelector(
selector.NumberSelectorConfig(
Expand Down Expand Up @@ -310,7 +314,7 @@ class LightColorMode(StrEnum):
SWITCH_KNX_SCHEMA = vol.Schema(
{
"section_switch": KNXSectionFlat(),
vol.Required(CONF_GA_SWITCH): GASelector(write_required=True),
vol.Required(CONF_GA_SWITCH): GASelector(write_required=True, valid_dpt="1"),
vol.Optional(CONF_INVERT, default=False): selector.BooleanSelector(),
vol.Optional(CONF_RESPOND_TO_READ, default=False): selector.BooleanSelector(),
vol.Optional(CONF_SYNC_STATE, default=True): SyncStateSelector(),
Expand Down
6 changes: 6 additions & 0 deletions homeassistant/components/litterrobot/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -209,5 +209,11 @@
}
}
}
},
"issues": {
"deprecated_entity": {
"title": "{name} is deprecated",
"description": "The Litter-Robot entity `{entity}` is deprecated and will be removed in a future release.\nPlease update your dashboards, automations and scripts, disable `{entity}` and reload the integration/restart Home Assistant to fix this issue."
}
}
}
77 changes: 67 additions & 10 deletions homeassistant/components/litterrobot/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,24 @@
from dataclasses import dataclass
from typing import Any, Generic

from pylitterbot import FeederRobot, LitterRobot, Robot
from pylitterbot import FeederRobot, LitterRobot, LitterRobot3, LitterRobot4, Robot

from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.components.switch import (
DOMAIN as SWITCH_DOMAIN,
SwitchEntity,
SwitchEntityDescription,
)
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.issue_registry import (
IssueSeverity,
async_create_issue,
async_delete_issue,
)

from .const import DOMAIN
from .coordinator import LitterRobotConfigEntry
from .entity import LitterRobotEntity, _WhiskerEntityT

Expand All @@ -26,6 +37,15 @@ class RobotSwitchEntityDescription(SwitchEntityDescription, Generic[_WhiskerEnti
value_fn: Callable[[_WhiskerEntityT], bool]


NIGHT_LIGHT_MODE_ENTITY_DESCRIPTION = RobotSwitchEntityDescription[
LitterRobot | FeederRobot
](
key="night_light_mode_enabled",
translation_key="night_light_mode",
set_fn=lambda robot, value: robot.set_night_light(value),
value_fn=lambda robot: robot.night_light_mode_enabled,
)

SWITCH_MAP: dict[type[Robot], tuple[RobotSwitchEntityDescription, ...]] = {
FeederRobot: (
RobotSwitchEntityDescription[FeederRobot](
Expand All @@ -34,14 +54,10 @@ class RobotSwitchEntityDescription(SwitchEntityDescription, Generic[_WhiskerEnti
set_fn=lambda robot, value: robot.set_gravity_mode(value),
value_fn=lambda robot: robot.gravity_mode_enabled,
),
NIGHT_LIGHT_MODE_ENTITY_DESCRIPTION,
),
LitterRobot3: (NIGHT_LIGHT_MODE_ENTITY_DESCRIPTION,),
Robot: ( # type: ignore[type-abstract] # only used for isinstance check
RobotSwitchEntityDescription[LitterRobot | FeederRobot](
key="night_light_mode_enabled",
translation_key="night_light_mode",
set_fn=lambda robot, value: robot.set_night_light(value),
value_fn=lambda robot: robot.night_light_mode_enabled,
),
RobotSwitchEntityDescription[LitterRobot | FeederRobot](
key="panel_lock_enabled",
translation_key="panel_lockout",
Expand All @@ -59,13 +75,54 @@ async def async_setup_entry(
) -> None:
"""Set up Litter-Robot switches using config entry."""
coordinator = entry.runtime_data
async_add_entities(
entities = [
RobotSwitchEntity(robot=robot, coordinator=coordinator, description=description)
for robot in coordinator.account.robots
for robot_type, entity_descriptions in SWITCH_MAP.items()
if isinstance(robot, robot_type)
for description in entity_descriptions
)
]

ent_reg = er.async_get(hass)

def add_deprecated_entity(
robot: LitterRobot4,
description: RobotSwitchEntityDescription,
entity_cls: type[RobotSwitchEntity],
) -> None:
"""Add deprecated entities."""
unique_id = f"{robot.serial}-{description.key}"
if entity_id := ent_reg.async_get_entity_id(SWITCH_DOMAIN, DOMAIN, unique_id):
entity_entry = ent_reg.async_get(entity_id)
if entity_entry and entity_entry.disabled:
ent_reg.async_remove(entity_id)
async_delete_issue(
hass,
DOMAIN,
f"deprecated_entity_{unique_id}",
)
elif entity_entry:
entities.append(entity_cls(robot, coordinator, description))
async_create_issue(
hass,
DOMAIN,
f"deprecated_entity_{unique_id}",
breaks_in_ha_version="2026.4.0",
is_fixable=False,
severity=IssueSeverity.WARNING,
translation_key="deprecated_entity",
translation_placeholders={
"name": f"{robot.name} {entity_entry.name or entity_entry.original_name}",
"entity": entity_id,
},
)

for robot in coordinator.account.get_robots(LitterRobot4):
add_deprecated_entity(
robot, NIGHT_LIGHT_MODE_ENTITY_DESCRIPTION, RobotSwitchEntity
)

async_add_entities(entities)


class RobotSwitchEntity(LitterRobotEntity[_WhiskerEntityT], SwitchEntity):
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/openai_conversation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ async def send_prompt(call: ServiceCall) -> ServiceResponse:

content.extend(
await async_prepare_files_for_prompt(
hass, [Path(filename) for filename in filenames]
hass, [(Path(filename), None) for filename in filenames]
)
)

Expand Down
37 changes: 21 additions & 16 deletions homeassistant/components/openai_conversation/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,15 +223,17 @@ def _convert_content_to_param(
ResponseReasoningItemParam(
type="reasoning",
id=content.native.id,
summary=[
{
"type": "summary_text",
"text": summary,
}
for summary in reasoning_summary
]
if content.thinking_content
else [],
summary=(
[
{
"type": "summary_text",
"text": summary,
}
for summary in reasoning_summary
]
if content.thinking_content
else []
),
encrypted_content=content.native.encrypted_content,
)
)
Expand Down Expand Up @@ -308,9 +310,11 @@ async def _transform_stream( # noqa: C901 - This is complex, but better to have
"tool_call_id": event.item.id,
"tool_name": "code_interpreter",
"tool_result": {
"output": [output.to_dict() for output in event.item.outputs] # type: ignore[misc]
if event.item.outputs is not None
else None
"output": (
[output.to_dict() for output in event.item.outputs] # type: ignore[misc]
if event.item.outputs is not None
else None
)
},
}
last_role = "tool_result"
Expand Down Expand Up @@ -529,7 +533,7 @@ async def _async_handle_chat_log(
if last_content.role == "user" and last_content.attachments:
files = await async_prepare_files_for_prompt(
self.hass,
[a.path for a in last_content.attachments],
[(a.path, a.mime_type) for a in last_content.attachments],
)
last_message = messages[-1]
assert (
Expand Down Expand Up @@ -601,7 +605,7 @@ async def _async_handle_chat_log(


async def async_prepare_files_for_prompt(
hass: HomeAssistant, files: list[Path]
hass: HomeAssistant, files: list[tuple[Path, str | None]]
) -> ResponseInputMessageContentListParam:
"""Append files to a prompt.

Expand All @@ -611,11 +615,12 @@ async def async_prepare_files_for_prompt(
def append_files_to_content() -> ResponseInputMessageContentListParam:
content: ResponseInputMessageContentListParam = []

for file_path in files:
for file_path, mime_type in files:
if not file_path.exists():
raise HomeAssistantError(f"`{file_path}` does not exist")

mime_type, _ = guess_file_type(file_path)
if mime_type is None:
mime_type = guess_file_type(file_path)[0]

if not mime_type or not mime_type.startswith(("image/", "application/pdf")):
raise HomeAssistantError(
Expand Down
Loading
Loading