186 files

This commit is contained in:
Home Assistant Version Control
2026-09-14 12:56:59 +00:00
parent 6be1e92941
commit bd8280defe
186 changed files with 14904 additions and 1087 deletions
@@ -49,6 +49,7 @@ class FeatureCentralPowerManager(BaseFeatureManager):
self._current_power: float | None = None
self._current_max_power: float | None = None
self._power_temp: float | None = None
self._power_unit_config: str | None = None
self._cancel_calculate_shedding_call = None
self._started_vtherm_total_power_by_id: dict[str, float] = {}
# Not used now
@@ -65,6 +66,7 @@ class FeatureCentralPowerManager(BaseFeatureManager):
self._power_sensor_entity_id = entry_infos.get(CONF_POWER_SENSOR)
self._max_power_sensor_entity_id = entry_infos.get(CONF_MAX_POWER_SENSOR)
self._power_temp = entry_infos.get(CONF_PRESET_POWER)
self._power_unit_config = to_internal_power_unit(entry_infos.get(CONF_POWER_UNIT))
self._is_configured = False
self._current_power = None
@@ -134,17 +136,27 @@ class FeatureCentralPowerManager(BaseFeatureManager):
# Retrieve current power
new_power = get_safe_float(self._hass, self._power_sensor_entity_id)
# Normalize to internal Watts based on sensor unit
if new_power is not None:
sensor_unit = self._resolve_sensor_unit(self._power_sensor_entity_id)
new_power = self.to_watts(new_power, sensor_unit)
power_changed = new_power is not None and self._current_power != new_power
if power_changed:
self._current_power = new_power
_LOGGER.debug("%s - New current power has been retrieved: %.3f", self, self._current_power)
_LOGGER.debug("%s - New current power has been retrieved: %.3f (normalized to Watts)", self, self._current_power)
# Retrieve max power
new_max_power = get_safe_float(self._hass, self._max_power_sensor_entity_id)
# Normalize to internal Watts based on sensor unit
if new_max_power is not None:
sensor_unit = self._resolve_sensor_unit(self._max_power_sensor_entity_id)
new_max_power = self.to_watts(new_max_power, sensor_unit)
max_power_changed = new_max_power is not None and self._current_max_power != new_max_power
if max_power_changed:
self._current_max_power = new_max_power
_LOGGER.debug("%s - New current max power has been retrieved: %.3f", self, self._current_max_power)
_LOGGER.debug("%s - New current max power has been retrieved: %.3f (normalized to Watts)", self, self._current_max_power)
# Schedule shedding calculation if there's any change
if power_changed or max_power_changed:
@@ -191,7 +203,7 @@ class FeatureCentralPowerManager(BaseFeatureManager):
device_power = vtherm.power_manager.device_power
total_power_gain += device_power
_LOGGER.info("vtherm %s should be in overpowering state (device_power=%.2f)", vtherm.name, device_power)
await vtherm.power_manager.set_overpowering(True, device_power)
await vtherm.power_manager.set_overpowering(True, vtherm.power_manager.device_power)
changed_vtherm.append(vtherm)
_LOGGER.debug("%s - after vtherm %s total_power_gain=%s, available_power=%s", self, vtherm.name, total_power_gain, available_power)
@@ -245,6 +257,17 @@ class FeatureCentralPowerManager(BaseFeatureManager):
await vtherm.update_states(force=True)
self._last_shedding_date = self._vtherm_api.now
# Additionally, clean up any VTherms that are in overpowering state but shouldn't be
# This handles cases where is_device_active changed after a VTherm was put in overpowering
if available_power >= 0:
all_vtherms = self.get_climate_components_entities()
for vtherm in all_vtherms:
if vtherm.power_manager.overpowering_state != STATE_OFF and vtherm.power_manager.is_configured:
# Check if this VTherm was already processed in the unshedding loop
if vtherm not in vtherms_sorted:
_LOGGER.debug("%s - Cleaning up vtherm %s from overpowering state (not in active list but has positive available power)", self, vtherm.name)
await vtherm.power_manager.set_overpowering(False)
# calculate a state as true if one of the VTherm is in shedding
self._state = any(vtherm.power_manager.is_overpowering_detected for vtherm in vtherms_sorted)
_LOGGER.debug("%s - -------- End of calculate_shedding", self)
@@ -325,14 +348,43 @@ class FeatureCentralPowerManager(BaseFeatureManager):
@property
def current_power(self) -> float | None:
"""Return the current power from sensor"""
"""Return the current power from sensor (normalized in Watts)"""
return self._current_power
@property
def current_max_power(self) -> float | None:
"""Return the current power from sensor"""
"""Return the current max power from sensor (normalized in Watts)"""
return self._current_max_power
def _resolve_sensor_unit(self, entity_id: str | None) -> str:
"""Resolve the power unit to use for a sensor.
Uses the user configured unit when set, else the sensor own
unit_of_measurement attribute, else falls back to Watts."""
if self._power_unit_config in (POWER_UNIT_WATT, POWER_UNIT_KILO_WATT):
return self._power_unit_config
if entity_id is not None and (state := self._hass.states.get(entity_id)) is not None:
unit = to_internal_power_unit(state.attributes.get("unit_of_measurement"))
if unit in (POWER_UNIT_WATT, POWER_UNIT_KILO_WATT):
return unit
return POWER_UNIT_WATT
@property
def power_unit(self) -> str:
"""Return the resolved central power unit ("W" or "kW")"""
return to_legal_power_unit(self._resolve_sensor_unit(self._power_sensor_entity_id))
def to_watts(self, power: float | None, unit: str | None) -> float | None:
"""Convert a power value to Watts"""
return power_to_watts(power, unit)
def from_watts(self, power_w: float | None, target_unit: str | None) -> float | None:
"""Convert a Watts value to the target power unit"""
return power_from_watts(power_w, target_unit)
@property
def power_temperature(self) -> float | None:
"""Return the power temperature"""