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
31 changes: 29 additions & 2 deletions homeassistant/components/duco/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,15 @@
DucoConnectionError,
DucoError,
DucoResponseError,
DucoUnsupportedCapabilityError,
)
from duco_connectivity.models import (
BoardInfo,
Node,
NodeListActionItemList,
NodeName,
VentilationTemperatureInfo,
)
from duco_connectivity.models import BoardInfo, Node, NodeListActionItemList, NodeName

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
Expand All @@ -26,14 +33,15 @@
type DucoConfigEntry = ConfigEntry[DucoCoordinator]


@dataclass
@dataclass(slots=True, kw_only=True)
class DucoData:
"""Data returned by the Duco coordinator."""

nodes: dict[int, Node]
node_actions: NodeListActionItemList
rssi_wifi: int | None
time_filter_remain: int | None
ventilation_temperatures: VentilationTemperatureInfo | None


class DucoCoordinator(DataUpdateCoordinator[DucoData]):
Expand All @@ -42,6 +50,7 @@ class DucoCoordinator(DataUpdateCoordinator[DucoData]):
config_entry: DucoConfigEntry
board_info: BoardInfo
_supports_time_filter_remain: bool
_supports_ventilation_temperatures: bool
_configured_node_names: dict[int, str]

def __init__(
Expand All @@ -61,6 +70,7 @@ def __init__(
self.client = client
self._configured_node_names = {}
self._supports_time_filter_remain = True
self._supports_ventilation_temperatures = True

async def _async_load_node_names(self) -> None:
"""Load configured Duco node names during setup."""
Expand Down Expand Up @@ -175,9 +185,26 @@ async def _async_update_data(self) -> DucoData:
time_filter_remain = await self.client.async_get_time_filter_remaining()
self._supports_time_filter_remain = time_filter_remain is not None

ventilation_temperatures = (
self.data.ventilation_temperatures if self.data else None
)
if self._supports_ventilation_temperatures:
try:
ventilation_temperatures = (
await self.client.async_get_ventilation_temperature_info()
)
except DucoUnsupportedCapabilityError:
ventilation_temperatures = None
self._supports_ventilation_temperatures = False
except DucoError as err:
_LOGGER.debug(
"Could not fetch Duco ventilation temperatures", exc_info=err
)

return DucoData(
nodes={node.node_id: node for node in nodes},
node_actions=node_actions,
rssi_wifi=rssi_wifi,
time_filter_remain=time_filter_remain,
ventilation_temperatures=ventilation_temperatures,
)
65 changes: 65 additions & 0 deletions homeassistant/components/duco/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
EntityCategory,
UnitOfRatio,
UnitOfTemperature,
UnitOfTime,
)
from homeassistant.core import HomeAssistant, callback
Expand Down Expand Up @@ -156,6 +157,70 @@ class DucoBoxSensorEntityDescription(SensorEntityDescription):
entity_registry_enabled_default=False,
value_fn=lambda coordinator: coordinator.data.rssi_wifi,
),
DucoBoxSensorEntityDescription(
key="outdoor_air_temperature",
translation_key="outdoor_air_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
supported_fn=lambda coordinator: (
coordinator.data.ventilation_temperatures is not None
and coordinator.data.ventilation_temperatures.temp_oda is not None
),
value_fn=lambda coordinator: (
coordinator.data.ventilation_temperatures.temp_oda
if coordinator.data.ventilation_temperatures
else None
),
),
DucoBoxSensorEntityDescription(
key="supply_air_temperature",
translation_key="supply_air_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
supported_fn=lambda coordinator: (
coordinator.data.ventilation_temperatures is not None
and coordinator.data.ventilation_temperatures.temp_sup is not None
),
value_fn=lambda coordinator: (
coordinator.data.ventilation_temperatures.temp_sup
if coordinator.data.ventilation_temperatures
else None
),
),
DucoBoxSensorEntityDescription(
key="extract_air_temperature",
translation_key="extract_air_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
supported_fn=lambda coordinator: (
coordinator.data.ventilation_temperatures is not None
and coordinator.data.ventilation_temperatures.temp_eta is not None
),
value_fn=lambda coordinator: (
coordinator.data.ventilation_temperatures.temp_eta
if coordinator.data.ventilation_temperatures
else None
),
),
DucoBoxSensorEntityDescription(
key="exhaust_air_temperature",
translation_key="exhaust_air_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
supported_fn=lambda coordinator: (
coordinator.data.ventilation_temperatures is not None
and coordinator.data.ventilation_temperatures.temp_eha is not None
),
value_fn=lambda coordinator: (
coordinator.data.ventilation_temperatures.temp_eha
if coordinator.data.ventilation_temperatures
else None
),
),
)


Expand Down
12 changes: 12 additions & 0 deletions homeassistant/components/duco/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@
}
},
"sensor": {
"exhaust_air_temperature": {
"name": "Exhaust air temperature"
},
"extract_air_temperature": {
"name": "Extract air temperature"
},
"filter_remaining": {
"name": "Filter remaining"
},
Expand All @@ -82,6 +88,12 @@
"iaq_rh": {
"name": "Humidity air quality index"
},
"outdoor_air_temperature": {
"name": "Outdoor air temperature"
},
"supply_air_temperature": {
"name": "Supply air temperature"
},
"target_flow_level": {
"name": "Target flow level"
},
Expand Down
31 changes: 31 additions & 0 deletions homeassistant/components/gatus/diagnostics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Diagnostics support for Gatus."""

from typing import Any

from homeassistant.core import HomeAssistant

from .coordinator import GatusConfigEntry


async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: GatusConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator = entry.runtime_data
return {
"data": [
{
"key": ep.key,
"name": ep.name,
"group": ep.group,
"results": [
{
"success": r.success,
"status": r.status,
}
for r in ep.results
],
}
for ep in coordinator.data.values()
],
}
2 changes: 1 addition & 1 deletion homeassistant/components/gatus/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ rules:

# Gold
devices: done
diagnostics: todo
diagnostics: done
discovery-update-info:
status: exempt
comment: Integration does not support discovery.
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/hydrawise/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"loggers": ["pydrawise"],
"requirements": ["pydrawise==2026.4.0"]
"requirements": ["pydrawise==2026.7.0"]
}
18 changes: 6 additions & 12 deletions homeassistant/components/portainer/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from typing import override

from pyportainer import StackType
from pyportainer.models.docker import DockerSystemDF
from pyportainer.models.docker import DockerContainerState, DockerSystemDF

from homeassistant.components.sensor import (
EntityCategory,
Expand Down Expand Up @@ -84,7 +84,7 @@ class PortainerVolumeSensorEntityDescription(SensorEntityDescription):
translation_key="container_state",
value_fn=lambda data: data.container.state,
device_class=SensorDeviceClass.ENUM,
options=["running", "exited", "paused", "restarting", "created", "dead"],
options=[state.value for state in DockerContainerState],
),
PortainerContainerSensorEntityDescription(
key="memory_limit",
Expand Down Expand Up @@ -315,17 +315,11 @@ class PortainerVolumeSensorEntityDescription(SensorEntityDescription):
PortainerStackSensorEntityDescription(
key="stack_type",
translation_key="stack_type",
value_fn=lambda data: (
"swarm"
if data.stack.type == StackType.SWARM
else "compose"
if data.stack.type == StackType.COMPOSE
else "kubernetes"
if data.stack.type == StackType.KUBERNETES
else None
),
value_fn=lambda data: {
stack.value: stack.name.lower() for stack in StackType
}.get(data.stack.type),
device_class=SensorDeviceClass.ENUM,
options=["swarm", "compose", "kubernetes"],
options=[stack.name.lower() for stack in StackType],
entity_category=EntityCategory.DIAGNOSTIC,
),
PortainerStackSensorEntityDescription(
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/portainer/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
"dead": "Dead",
"exited": "Exited",
"paused": "Paused",
"removing": "Removing",
"restarting": "Restarting",
"running": "Running"
}
Expand Down
14 changes: 1 addition & 13 deletions homeassistant/components/portainer/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,7 @@ def _async_add_new_containers(
class PortainerContainerImageUpdateEntity(PortainerContainerEntity, UpdateEntity):
"""Representation of a Portainer container update."""

_attr_supported_features = (
UpdateEntityFeature.INSTALL | UpdateEntityFeature.PROGRESS
)
_attr_supported_features = UpdateEntityFeature.INSTALL

entity_description: PortainerContainerUpdateEntityDescription

Expand All @@ -130,7 +128,6 @@ def __init__(
super().__init__(coordinator, entity_description, device_info, via_device)

self._attr_unique_id = f"{coordinator.config_entry.entry_id}_{self.device_name}_{entity_description.key}"
self._in_progress_old_version: str | None = None

@override
@property
Expand All @@ -152,18 +149,11 @@ def latest_version(self) -> str | None:
"""Return latest version."""
return self.entity_description.latest_version(self.container_data.image_status)

@override
@property
def in_progress(self) -> bool:
"""Return if an update is in progress."""
return self._in_progress_old_version == self.installed_version

@override
async def async_install(
self, version: str | None, backup: bool, **kwargs: Any
) -> None:
"""Install update."""
self._in_progress_old_version = self.installed_version
try:
await self.entity_description.update_func(
self.coordinator.portainer,
Expand All @@ -183,5 +173,3 @@ async def async_install(
) from ex
else:
await self.coordinator.async_request_refresh()
finally:
self._in_progress_old_version = None
13 changes: 8 additions & 5 deletions homeassistant/components/roborock/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ class RoborockBinarySensorDescriptionA01(BinarySensorEntityDescription):
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda data: data.status.dirty_water_box_status,
is_dock_entity=True,
support_fn=lambda api: api.wash_towel_mode is not None,
support_fn=lambda api: api.device_features.is_field_supported(
StatusV2, StatusField.DIRTY_WATER_BOX_STATUS
),
),
RoborockBinarySensorDescription(
key="clean_box_empty",
Expand All @@ -107,7 +109,9 @@ class RoborockBinarySensorDescriptionA01(BinarySensorEntityDescription):
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda data: data.status.clear_water_box_status,
is_dock_entity=True,
support_fn=lambda api: api.wash_towel_mode is not None,
support_fn=lambda api: api.device_features.is_field_supported(
StatusV2, StatusField.CLEAR_WATER_BOX_STATUS
),
),
RoborockBinarySensorDescription(
key="clean_fluid_empty",
Expand All @@ -120,9 +124,8 @@ class RoborockBinarySensorDescriptionA01(BinarySensorEntityDescription):
else None
),
is_dock_entity=True,
support_fn=lambda api: (
api.wash_towel_mode is not None
and api.device_features.is_clean_fluid_delivery_supported
support_fn=lambda api: api.device_features.is_field_supported(
StatusV2, StatusField.CLEAN_FLUID_STATUS
),
),
RoborockBinarySensorDescription(
Expand Down
7 changes: 4 additions & 3 deletions homeassistant/components/roborock/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
ZeoState,
)
from roborock.data.b01_q10.b01_q10_code_mappings import YXDeviceState
from roborock.data.v1.v1_containers import StatusField, StatusV2
from roborock.devices.traits.b01.q10.status import StatusTrait as Q10StatusTrait
from roborock.devices.traits.v1 import PropertiesApi
from roborock.roborock_message import RoborockDyadDataProtocol, RoborockZeoProtocol
Expand Down Expand Up @@ -259,9 +260,9 @@ def _dock_error_value_fn(state: DeviceState) -> str | None:
device_class=SensorDeviceClass.ENUM,
options=RoborockDockErrorCode.keys(),
is_dock_entity=True,
# Only available with more than just the basic dock. Dust collection
# mode is a proxy for any more complex dock type (e.g. Auto-empty).
support_fn=lambda api: api.dust_collection_mode is not None,
support_fn=lambda api: api.device_features.is_field_supported(
StatusV2, StatusField.DOCK_ERROR_STATUS
),
),
RoborockSensorDescription(
key="mop_clean_remaining",
Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt

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

Loading
Loading