1
0
mirror of https://github.com/home-assistant/core.git synced 2026-04-17 23:53:49 +01:00
Files
core/homeassistant/components/bsblan/water_heater.py
2026-02-24 18:09:32 +01:00

174 lines
6.1 KiB
Python

"""BSBLAN platform to control a compatible Water Heater Device."""
from __future__ import annotations
from typing import Any
from bsblan import BSBLANError, SetHotWaterParam
from homeassistant.components.water_heater import (
STATE_ECO,
STATE_OFF,
STATE_PERFORMANCE,
WaterHeaterEntity,
WaterHeaterEntityFeature,
)
from homeassistant.const import ATTR_TEMPERATURE
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import BSBLanConfigEntry, BSBLanData
from .const import DOMAIN
from .entity import BSBLanDualCoordinatorEntity
PARALLEL_UPDATES = 1
# Mapping between BSBLan operating mode values and HA operation modes
BSBLAN_TO_HA_OPERATION_MODE: dict[int, str] = {
0: STATE_OFF, # Protection mode
1: STATE_PERFORMANCE, # Continuous comfort mode
2: STATE_ECO, # Eco/automatic mode
}
HA_TO_BSBLAN_OPERATION_MODE: dict[str, int] = {
v: k for k, v in BSBLAN_TO_HA_OPERATION_MODE.items()
}
async def async_setup_entry(
hass: HomeAssistant,
entry: BSBLanConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up BSBLAN water heater based on a config entry."""
data = entry.runtime_data
# Only create water heater entity if DHW (Domestic Hot Water) is available
# Check if we have any DHW-related data indicating water heater support
dhw_data = data.fast_coordinator.data.dhw
if (
dhw_data.operating_mode is None
and dhw_data.nominal_setpoint is None
and dhw_data.dhw_actual_value_top_temperature is None
):
# No DHW functionality available, skip water heater setup
return
async_add_entities([BSBLANWaterHeater(data)])
class BSBLANWaterHeater(BSBLanDualCoordinatorEntity, WaterHeaterEntity):
"""Defines a BSBLAN water heater entity."""
_attr_name = None
_attr_operation_list = list(HA_TO_BSBLAN_OPERATION_MODE.keys())
_attr_supported_features = (
WaterHeaterEntityFeature.TARGET_TEMPERATURE
| WaterHeaterEntityFeature.OPERATION_MODE
| WaterHeaterEntityFeature.ON_OFF
)
def __init__(self, data: BSBLanData) -> None:
"""Initialize BSBLAN water heater."""
super().__init__(data.fast_coordinator, data.slow_coordinator, data)
self._attr_unique_id = format_mac(data.device.MAC)
# Set temperature unit
self._attr_temperature_unit = data.fast_coordinator.client.get_temperature_unit
# Initialize available attribute to resolve multiple inheritance conflict
self._attr_available = True
# Set temperature limits based on device capabilities from slow coordinator
dhw_config = (
data.slow_coordinator.data.dhw_config
if data.slow_coordinator.data
else None
)
# For min_temp: Use reduced_setpoint from config data (slow polling)
if (
dhw_config is not None
and dhw_config.reduced_setpoint is not None
and dhw_config.reduced_setpoint.value is not None
):
self._attr_min_temp = dhw_config.reduced_setpoint.value
else:
self._attr_min_temp = 10.0 # Default minimum
# For max_temp: Use nominal_setpoint_max from config data (slow polling)
if (
dhw_config is not None
and dhw_config.nominal_setpoint_max is not None
and dhw_config.nominal_setpoint_max.value is not None
):
self._attr_max_temp = dhw_config.nominal_setpoint_max.value
else:
self._attr_max_temp = 65.0 # Default maximum
@property
def current_operation(self) -> str | None:
"""Return current operation."""
if (
operating_mode := self.coordinator.data.dhw.operating_mode
) is None or operating_mode.value is None:
return None
return BSBLAN_TO_HA_OPERATION_MODE.get(operating_mode.value)
@property
def current_temperature(self) -> float | None:
"""Return the current temperature."""
if (
current_temp := self.coordinator.data.dhw.dhw_actual_value_top_temperature
) is None:
return None
return current_temp.value
@property
def target_temperature(self) -> float | None:
"""Return the temperature we try to reach."""
if (target_temp := self.coordinator.data.dhw.nominal_setpoint) is None:
return None
return target_temp.value
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
try:
await self.coordinator.client.set_hot_water(
SetHotWaterParam(nominal_setpoint=temperature)
)
except BSBLANError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_temperature_error",
) from err
await self.coordinator.async_request_refresh()
async def async_set_operation_mode(self, operation_mode: str) -> None:
"""Set new operation mode."""
# Base class validates operation_mode is in operation_list before calling
bsblan_mode = HA_TO_BSBLAN_OPERATION_MODE[operation_mode]
try:
# Send numeric value as string - BSB-LAN API expects numeric mode values
await self.coordinator.client.set_hot_water(
SetHotWaterParam(operating_mode=str(bsblan_mode))
)
except BSBLANError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_operation_mode_error",
) from err
await self.coordinator.async_request_refresh()
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the water heater on."""
await self.async_set_operation_mode(STATE_PERFORMANCE)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the water heater off."""
await self.async_set_operation_mode(STATE_OFF)