291 files

This commit is contained in:
Home Assistant Version Control
2026-10-14 18:10:18 +00:00
parent c4e0178d8a
commit 280501d2e8
291 changed files with 32540 additions and 3280 deletions
@@ -1904,6 +1904,9 @@ class AutoTpiManager:
async def _detect_failures(self, current_temp_in: float):
"""Detect system failures."""
if not self.learning_active:
return
OFFSET_FAILURE = 1.0
MIN_LEARN_FOR_DETECTION = 25
@@ -3119,6 +3122,7 @@ class AutoTpiManager:
)
# Update status to indicate learning has resumed
self.state.last_learning_status = "learning_resumed"
self.state.consecutive_failures = 0
# Always enable learning when activating
self.state.autolearn_enabled = True
@@ -899,9 +899,16 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
# transform _attr_preset_modes into _vtherm_preset_modes
self._vtherm_preset_modes = [VThermPreset(mode) for mode in self._attr_preset_modes]
# Re-applicate the last preset if any to take change into account
if self._state_manager.current_state.preset and self._state_manager.current_state.preset != VThermPreset.NONE:
await self.async_set_preset_mode_internal(self._state_manager.current_state.preset)
# Re-apply the requested visible preset, if it is still configured.
# The current preset can be a temporary hidden override (safety/power)
# and must never become the requested user preset during initialization.
requested_preset = self._state_manager.requested_state.preset
if (
requested_preset != VThermPreset.NONE
and requested_preset in self._vtherm_preset_modes
and requested_preset not in HIDDEN_PRESETS
):
await self.async_set_preset_mode_internal(requested_preset)
##
## Properties overriden from ClimateEntity (or not)
@@ -2291,9 +2298,9 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
async def service_set_safety(
self,
delay_min: int,
min_on_percent: float,
default_on_percent: float,
delay_min: int | None = None,
min_on_percent: float | None = None,
default_on_percent: float | None = None,
):
"""Called by a service call:
service: versatile_thermostat.set_safety
@@ -2307,13 +2314,17 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
if self.lock_manager.check_is_locked("service_set_safety"):
return
write_event_log(
_LOGGER, self, f"Calling SERVICE_SET_SAFETY, delay_min: {delay_min}, " f"min_on_percent: {min_on_percent * 100} %, default_on_percent: {default_on_percent * 100} %"
_LOGGER,
self,
f"Calling SERVICE_SET_SAFETY, delay_min: {delay_min}, "
f"min_on_percent: {min_on_percent * 100 if min_on_percent is not None else 'unchanged'} %, "
f"default_on_percent: {default_on_percent * 100 if default_on_percent is not None else 'unchanged'} %",
)
if delay_min:
if delay_min is not None:
self._safety_manager.set_safety_delay_min(delay_min)
if min_on_percent:
if min_on_percent is not None:
self._safety_manager.set_safety_min_on_percent(min_on_percent)
if default_on_percent:
if default_on_percent is not None:
self._safety_manager.set_safety_default_on_percent(default_on_percent)
await self.async_control_heating()
@@ -418,24 +418,27 @@ class FeatureCentralBoilerManager(BaseFeatureManager):
if self._is_configured and not force:
return self._all_boiler_entities
required_entities = {
"number activation threshold": self._nb_active_device_threshold_number_entity,
"power activation threshold": self._total_power_active_threshold_number_entity,
"number of active devices": self._nb_active_device_number_entity,
"total active power": self._total_power_active_entity,
}
self._all_boiler_entities = []
if self._nb_active_device_threshold_number_entity:
self._all_boiler_entities.append(self._nb_active_device_threshold_number_entity.entity_id)
if self._total_power_active_threshold_number_entity:
self._all_boiler_entities.append(self._total_power_active_threshold_number_entity.entity_id)
if self._nb_active_device_number_entity:
self._all_boiler_entities.append(self._nb_active_device_number_entity.entity_id)
if self._total_power_active_entity:
self._all_boiler_entities.append(self._total_power_active_entity.entity_id)
for entity in required_entities.values():
if entity:
self._all_boiler_entities.append(entity.entity_id)
old_ready = self._is_ready
self._is_ready = self.is_configured and len(self._all_boiler_entities) == 4 and self._central_boiler_entity is not None
if not self._is_ready and self.is_configured:
missing_entities = [name for name, entity in required_entities.items() if entity is None]
_LOGGER.warning(
"%s - central boiler manager is not fully configured. Found only %d/4 entities and central boiler entity=%s. Central boiler control will not work properly. This could a temporary message at startup.",
"%s - central boiler manager is not fully configured. Found only %d/4 entities and central boiler entity=%s. Missing entities: %s. Central boiler control will not work properly. This could a temporary message at startup.",
self,
len(self._all_boiler_entities),
self._central_boiler_entity,
", ".join(missing_entities) or "none",
)
return []
if not self._is_ready and not self.is_configured:
@@ -22,6 +22,6 @@
"vtherm_api>=0.5.0"
],
"ssdp": [],
"version": "10.5.0",
"version": "10.5.1",
"zeroconf": []
}
@@ -151,10 +151,11 @@ async def async_setup_entry(
class EnergySensor(VersatileThermostatBaseEntity, SensorEntity):
"""Representation of a Energy sensor which exposes the energy"""
_attr_translation_key = "energy"
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
"""Initialize the energy sensor"""
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
self._attr_name = "Energy"
self._attr_unique_id = f"{self._device_name}_energy"
@callback
@@ -210,10 +211,11 @@ class EnergySensor(VersatileThermostatBaseEntity, SensorEntity):
class MeanPowerSensor(VersatileThermostatBaseEntity, SensorEntity):
"""Representation of a power sensor which exposes the mean power in a cycle"""
_attr_translation_key = "mean_power_cycle"
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
"""Initialize the energy sensor"""
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
self._attr_name = "Mean power cycle"
self._attr_unique_id = f"{self._device_name}_mean_power_cycle"
@callback
@@ -272,10 +274,11 @@ class MeanPowerSensor(VersatileThermostatBaseEntity, SensorEntity):
class OnPercentSensor(VersatileThermostatBaseEntity, SensorEntity):
"""Representation of a on percent sensor which exposes the on_percent in a cycle"""
_attr_translation_key = "power_percent"
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
"""Initialize the energy sensor"""
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
self._attr_name = "Power percent"
self._attr_unique_id = f"{self._device_name}_power_percent"
@callback
@@ -330,10 +333,11 @@ class OnPercentSensor(VersatileThermostatBaseEntity, SensorEntity):
class AutoTpiSensor(VersatileThermostatBaseEntity, SensorEntity):
"""Representation of the Auto TPI Learning state"""
_attr_translation_key = "auto_tpi_learning"
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
"""Initialize the Auto TPI sensor"""
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
self._attr_name = "Auto TPI Learning State"
self._attr_unique_id = f"{self._device_name}_auto_tpi_learning"
self._attr_icon = "mdi:brain"
@@ -358,11 +362,11 @@ class AutoTpiSensor(VersatileThermostatBaseEntity, SensorEntity):
# Determine state
if manager.learning_active:
if manager.is_in_bootstrap:
self._attr_native_value = "Bootstrap"
self._attr_native_value = "bootstrap"
else:
self._attr_native_value = "Active"
self._attr_native_value = "active"
else:
self._attr_native_value = "Off" # Or "Completed" / "Idle" depending on context, but "Off" implies not learning.
self._attr_native_value = "off" # Or "Completed" / "Idle" depending on context, but "Off" implies not learning.
# Update attributes
# Update attributes
@@ -405,10 +409,11 @@ class AutoTpiSensor(VersatileThermostatBaseEntity, SensorEntity):
class ValveOpenPercentSensor(VersatileThermostatBaseEntity, SensorEntity):
"""Representation of a on percent sensor which exposes the on_percent in a cycle"""
_attr_translation_key = "valve_open_percent"
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
"""Initialize the energy sensor"""
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
self._attr_name = "Valve open percent"
self._attr_unique_id = f"{self._device_name}_valve_open_percent"
@callback
@@ -451,10 +456,11 @@ class ValveOpenPercentSensor(VersatileThermostatBaseEntity, SensorEntity):
class OnTimeSensor(VersatileThermostatBaseEntity, SensorEntity):
"""Representation of a on time sensor which exposes the on_time_sec in a cycle"""
_attr_translation_key = "on_time"
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
"""Initialize the energy sensor"""
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
self._attr_name = "On time"
self._attr_unique_id = f"{self._device_name}_on_time"
@callback
@@ -500,10 +506,11 @@ class OnTimeSensor(VersatileThermostatBaseEntity, SensorEntity):
class OffTimeSensor(VersatileThermostatBaseEntity, SensorEntity):
"""Representation of a on time sensor which exposes the off_time_sec in a cycle"""
_attr_translation_key = "off_time"
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
"""Initialize the energy sensor"""
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
self._attr_name = "Off time"
self._attr_unique_id = f"{self._device_name}_off_time"
@callback
@@ -548,10 +555,11 @@ class OffTimeSensor(VersatileThermostatBaseEntity, SensorEntity):
class LastTemperatureSensor(VersatileThermostatBaseEntity, SensorEntity):
"""Representation of a last temperature datetime sensor"""
_attr_translation_key = "last_temperature_date"
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
"""Initialize the last temperature datetime sensor"""
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
self._attr_name = "Last temperature date"
self._attr_entity_registry_enabled_default = False
self._attr_unique_id = f"{self._device_name}_last_temp_datetime"
@@ -578,10 +586,11 @@ class LastTemperatureSensor(VersatileThermostatBaseEntity, SensorEntity):
class LastExtTemperatureSensor(VersatileThermostatBaseEntity, SensorEntity):
"""Representation of a last external temperature datetime sensor"""
_attr_translation_key = "last_external_temperature_date"
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
"""Initialize the last temperature datetime sensor"""
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
self._attr_name = "Last external temperature date"
self._attr_entity_registry_enabled_default = False
self._attr_unique_id = f"{self._device_name}_last_ext_temp_datetime"
@@ -608,10 +617,11 @@ class LastExtTemperatureSensor(VersatileThermostatBaseEntity, SensorEntity):
class TemperatureSlopeSensor(VersatileThermostatBaseEntity, SensorEntity):
"""Representation of a sensor which exposes the temperature slope curve"""
_attr_translation_key = "temperature_slope"
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
"""Initialize the slope sensor"""
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
self._attr_name = "Temperature slope"
self._attr_unique_id = f"{self._device_name}_temperature_slope"
@callback
@@ -665,10 +675,11 @@ class TemperatureSlopeSensor(VersatileThermostatBaseEntity, SensorEntity):
class RegulatedTemperatureSensor(VersatileThermostatBaseEntity, SensorEntity):
"""Representation of a Energy sensor which exposes the energy"""
_attr_translation_key = "regulated_temperature"
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
"""Initialize the regulated temperature sensor"""
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
self._attr_name = "Regulated temperature"
self._attr_unique_id = f"{self._device_name}_regulated_temperature"
@callback
@@ -716,10 +727,11 @@ class RegulatedTemperatureSensor(VersatileThermostatBaseEntity, SensorEntity):
class EMATemperatureSensor(VersatileThermostatBaseEntity, SensorEntity):
"""Representation of a Exponential Moving Average temp"""
_attr_translation_key = "ema_temperature"
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
"""Initialize the regulated temperature sensor"""
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
self._attr_name = "EMA temperature"
self._attr_unique_id = f"{self._device_name}_ema_temperature"
@callback
@@ -768,6 +780,10 @@ class NbActiveDeviceForBoilerSensor(SensorEntity):
"""Representation of the number of VTherm
which are active and configured to activate the boiler"""
_attr_has_entity_name = True
_attr_translation_key = "nb_device_active_boiler"
# TODO remove all listener mecanisms
_entity_component_unrecorded_attributes = SensorEntity._entity_component_unrecorded_attributes.union( # pylint: disable=protected-access
frozenset({"active_device_ids"})
@@ -778,7 +794,6 @@ class NbActiveDeviceForBoilerSensor(SensorEntity):
self._hass = hass
self._config_id = unique_id
self._device_name = entry_infos.get(CONF_NAME)
self._attr_name = "Nb device active for boiler"
self._attr_unique_id = "nb_device_active_boiler"
self._attr_value = self._attr_native_value = None # default value
self._entities = []
@@ -951,10 +966,11 @@ class TotalPowerActiveDeviceForBoilerSensor(NbActiveDeviceForBoilerSensor):
"""Representation of the total power of VTherm
which are active and configured to activate the boiler"""
_attr_translation_key = "total_power_active_boiler"
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
"""Initialize the energy sensor"""
super().__init__(hass, unique_id, name, entry_infos)
self._attr_name = "Total power active for boiler"
self._attr_unique_id = "total_power_active_boiler"
self._attr_value = self._attr_native_value = None # default value
self._cancel_listener_power: callable | None = None
File diff suppressed because it is too large Load Diff
@@ -151,7 +151,7 @@ class ThermostatOverClimateValve(ThermostatProp[UnderlyingClimate], ThermostatOv
for under in self._underlyings_valve_regulation:
_LOGGER.debug("%s - starting underlying valve regulation %s", self, under)
try:
under.startup()
await under.async_startup()
_LOGGER.debug("%s - underlying valve regulation %s started successfully", self, under)
except Exception as ex: # pylint: disable=broad-except
_LOGGER.error("%s - Error starting underlying valve regulation %s: %s", self, under, ex)
@@ -190,6 +190,7 @@ class ThermostatOverClimateValve(ThermostatProp[UnderlyingClimate], ThermostatOv
"last_sent_opening_value": under.last_sent_opening_value,
"min_opening_degree": under._min_opening_degree, # pylint: disable=protected-access
"max_opening_degree": under._max_opening_degree, # pylint: disable=protected-access
**under.bootstrap_diagnostics,
}
}
)
@@ -358,6 +358,7 @@
"heater_heating_time": "Doba ohřevu (min)",
"heater_cooling_time": "Doba chlazení (min)",
"auto_tpi_heating_rate": "Rychlost ohřevu ({unit}/h)",
"auto_tpi_cooling_rate": "Rychlost chlazení ({unit}/h)",
"auto_tpi_aggressiveness": "Agresivita",
"auto_tpi_enable_advanced_settings": "Povolit pokročilé parametry",
"auto_tpi_continuous_kext": "Průběžné učení Kext"
@@ -367,6 +368,7 @@
"heater_heating_time": "Čas k dosažení plného výkonu (minuty)",
"heater_cooling_time": "Čas na ochlazení po zastavení (minuty)\n\n| Typ | Doba ohřevu | Doba chlazení |\n| :--- | :--- | :--- |\n| Elektrický radiátor | 5 min | 7mn |\n| Vodní radiátor | 15 minut | 20 min |\n| Podlahové vytápění | 30 min | 45 min |",
"auto_tpi_heating_rate": "Kapacita nárůstu teploty radiátoru ({unit} za hodinu). Ponechejte na 0 pro automatické učení (bootstrap).",
"auto_tpi_cooling_rate": "Kapacita poklesu teploty radiátoru ({unit} za hodinu). Ponechejte na 0 pro automatické učení (bootstrap).",
"auto_tpi_aggressiveness": "Redukční faktor pro naučené koeficienty (50-100%). Nižší hodnoty vedou ke konzervativnějším koeficientům a snižují riziko přestřelení.",
"auto_tpi_enable_advanced_settings": "Zaškrtněte pro přístup k jemnému doladění parametrů (Alpha, Decay, Weight).",
"auto_tpi_continuous_kext": "Zaškrtnutím povolíte průběžné učení externího koeficientu (Kext) mimo relace AutoTPI."
@@ -768,6 +770,7 @@
"data": {
"auto_tpi_learning_type": "Typ učení",
"auto_tpi_heating_rate": "Rychlost ohřevu ({unit}/h)",
"auto_tpi_cooling_rate": "Rychlost chlazení ({unit}/h)",
"heater_heating_time": "Doba ohřevu (min)",
"heater_cooling_time": "Doba chlazení (min)",
"auto_tpi_aggressiveness": "Agresivita",
@@ -777,6 +780,7 @@
"data_description": {
"auto_tpi_learning_type": "Zvolte 'Discovery' pro počáteční spuštění (vážený průměr, váha 1) nebo 'Fine Tuning' pro průběžné úpravy (EWMA, Alpha 0,08).",
"auto_tpi_heating_rate": "Kapacita nárůstu teploty radiátoru ({unit} za hodinu). Ponechejte na 0 pro automatické učení (bootstrap).",
"auto_tpi_cooling_rate": "Kapacita poklesu teploty radiátoru ({unit} za hodinu). Ponechejte na 0 pro automatické učení (bootstrap).",
"heater_heating_time": "Čas k dosažení plného výkonu (minuty)",
"heater_cooling_time": "Čas na ochlazení po zastavení (minuty)\n\n| Typ | Doba ohřevu | Doba chlazení |\n| :--- | :--- | :--- |\n| Elektrický radiátor | 5 min | 7mn |\n| Vodní radiátor | 15 minut | 20 min |\n| Podlahové vytápění | 30 min | 45 min |",
"auto_tpi_aggressiveness": "Redukční faktor pro naučené koeficienty (50-100%). Nižší hodnoty vedou ke konzervativnějším koeficientům a snižují riziko přestřelení.",
@@ -312,6 +312,7 @@
"heater_heating_time": "Aufheizzeit (min)",
"heater_cooling_time": "Auskühlzeit (min)",
"auto_tpi_heating_rate": "Aufheizgeschwindigkeit ({unit}/h)",
"auto_tpi_cooling_rate": "Abkühlrate ({unit}/h)",
"auto_tpi_aggressiveness": "Aggressivität",
"auto_tpi_enable_advanced_settings": "Erweiterte Einstellungen aktivieren",
"auto_tpi_continuous_kext": "Kontinuierliches Kext-lernen"
@@ -321,6 +322,7 @@
"heater_heating_time": "Zeit bis zum Erreichen der vollen Leistung (Minuten)",
"heater_cooling_time": "Abkühlzeit nach dem Ausschalten (Minuten)\n\n| Typ | Aufheizzeit | Abkühlzeit |\n| :--- | :--- | :--- |\n| Elektroheizkörper | 5 Min. | 7 Min. |\n| Wasserheizkörper | 15 Min. | 20 Min. |\n| Fußbodenheizung | 30 Min. | 45 Min. |",
"auto_tpi_heating_rate": "Temperaturanstiegskapazität des Heizkörpers ({unit} pro Stunde). Kann über den Aktionsdienst 'Kapazität kalibrieren' geschätzt werden.",
"auto_tpi_cooling_rate": "Abkühlleistung des Heizkörpers ({unit} pro Stunde). Für automatisches Lernen (Bootstrap) auf 0 belassen.",
"auto_tpi_aggressiveness": "Skalierungsfaktor für die gelernten Koeffizienten (50-100 %). Niedrigere Werte führen zu konservativeren Koeffizienten und verringern das Risiko einer Überschreitung des Sollwerts.",
"auto_tpi_enable_advanced_settings": "Option anwählen, um die Feinabstimmung der Parameter (Alpha, Abklingen, Gewicht) vorzunehmen.",
"auto_tpi_continuous_kext": "Kontrollkästchen aktivieren, um das kontinuierliche Lernen des externen Koeffizienten (Kext) außerhalb von AutoTPI-Sitzungen zu ermöglichen."
@@ -746,6 +748,7 @@
"data": {
"auto_tpi_learning_type": "Art des Lernens",
"auto_tpi_heating_rate": "Aufheizrate ({unit}/h)",
"auto_tpi_cooling_rate": "Abkühlrate ({unit}/h)",
"heater_heating_time": "Aufheizzeit (min)",
"heater_cooling_time": "Abkühlzeit (min)",
"auto_tpi_aggressiveness": "Aggressivität",
@@ -755,6 +758,7 @@
"data_description": {
"auto_tpi_learning_type": "Wähle 'Erkundung' für den ersten Start (gewichteter Durchschnitt, Gewicht 1) oder 'Feinabstimmung' für kontinuierliche Anpassungen (EWMA, Alpha 0,08).",
"auto_tpi_heating_rate": "Temperaturanstiegskapazität des Heizkörpers ({unit} pro Stunde). Kann über den Aktionsdienst 'Kapazität kalibrieren' geschätzt werden.",
"auto_tpi_cooling_rate": "Abkühlleistung des Heizkörpers ({unit} pro Stunde). Für automatisches Lernen (Bootstrap) auf 0 belassen.",
"heater_heating_time": "Zeit bis zum Erreichen der vollen Leistung (Minuten)",
"heater_cooling_time": "Abkühlzeit nach dem Ausschalten (Minuten)\n\n| Typ | Aufheizzeit | Abkühlzeit |\n| :--- | :--- | :--- |\n| Elektroheizkörper | 5 Min. | 7 Min. |\n| Wasserheizkörper | 15 Min. | 20 Min. |\n| Fußbodenheizung | 30 Min. | 45 Min. |",
"auto_tpi_aggressiveness": "Skalierungsfaktor für die gelernten Koeffizienten (50–100 %). Niedrigere Werte führen zu konservativeren Koeffizienten und verringern das Risiko einer Überschreitung des Sollwerts.",
@@ -948,6 +952,56 @@
"name": "Zentralheizungskessel"
}
},
"sensor": {
"energy": {
"name": "Energie"
},
"mean_power_cycle": {
"name": "Mittlere Zyklusleistung"
},
"power_percent": {
"name": "Leistungsanteil"
},
"auto_tpi_learning": {
"name": "Auto-TPI-Lernstatus",
"state": {
"disabled": "Deaktiviert",
"bootstrap": "Initialisierung",
"active": "Aktiv",
"off": "Aus"
}
},
"valve_open_percent": {
"name": "Ventilöffnungsgrad"
},
"on_time": {
"name": "Einschaltzeit"
},
"off_time": {
"name": "Ausschaltzeit"
},
"last_temperature_date": {
"name": "Zeitpunkt der letzten Temperaturmessung"
},
"last_external_temperature_date": {
"name": "Zeitpunkt der letzten Außentemperaturmessung"
},
"temperature_slope": {
"name": "Temperaturänderungsrate"
},
"regulated_temperature": {
"name": "Geregelte Temperatur"
},
"ema_temperature": {
"name": "EMA-Temperatur"
},
"nb_device_active_boiler": {
"name": "Anzahl aktiver Geräte für Heizkessel"
},
"total_power_active_boiler": {
"name": "Gesamte aktive Leistung für Heizkessel"
}
},
"number": {
"frost_temp": {
"name": "Frostschutz"
@@ -248,6 +248,14 @@
"max_closing_degree": "Μέγιστη τιμή του βαθμού κλεισίματος. Η βαλβίδα δεν θα κλείσει ποτέ περισσότερο από αυτήν την τιμή. Ορίστε το στο 100 για πλήρες κλείσιμο της βαλβίδας"
}
},
"auto_tpi_configuration": {
"data": {
"auto_tpi_cooling_rate": "Ρυθμός ψύξης ({unit}/h)"
},
"data_description": {
"auto_tpi_cooling_rate": "Ικανότητα ψύξης του καλοριφέρ ({unit} ανά ώρα). Αφήστε την τιμή στο 0 για αυτόματη εκμάθηση (bootstrap)."
}
},
"heating_failure_detection": {
"title": "Heating failure detection",
"description": "Configure the heating failure detection feature. This detects anomalies when heating is expected but temperature doesn't increase, or when heating is off but temperature keeps rising.",
@@ -525,6 +533,14 @@
"max_closing_degree": "Μέγιστη τιμή του βαθμού κλεισίματος. Η βαλβίδα δεν θα κλείσει ποτέ περισσότερο από αυτήν την τιμή. Ορίστε το στο 100 για πλήρες κλείσιμο της βαλβίδας"
}
},
"auto_tpi_configuration": {
"data": {
"auto_tpi_cooling_rate": "Ρυθμός ψύξης ({unit}/h)"
},
"data_description": {
"auto_tpi_cooling_rate": "Ικανότητα ψύξης του καλοριφέρ ({unit} ανά ώρα). Αφήστε την τιμή στο 0 για αυτόματη εκμάθηση (bootstrap)."
}
},
"heating_failure_detection": {
"title": "Heating failure detection",
"description": "Configure the heating failure detection feature. This detects anomalies when heating is expected but temperature doesn't increase, or when heating is off but temperature keeps rising.",
@@ -628,4 +644,4 @@
"description": "Θέτει ένα VTherm σε εποχική αναστολή. Εμφανίζεται ως απενεργοποιημένο και, για over_climate με άμεσο έλεγχο βαλβίδας και over_valve, στέλνει ακατέργαστο αίτημα ανοίγματος βαλβίδας 100%. Το φυσικό άνοιγμα εξακολουθεί να υπόκειται στον ρυθμισμένο έλεγχο βαλβίδας και στα όρια της οντότητας number."
}
}
}
}
@@ -312,6 +312,7 @@
"heater_heating_time": "Heating time (min)",
"heater_cooling_time": "Cooling time (min)",
"auto_tpi_heating_rate": "Heating Rate ({unit}/h)",
"auto_tpi_cooling_rate": "Cooling Rate ({unit}/h)",
"auto_tpi_aggressiveness": "Aggressiveness",
"auto_tpi_enable_advanced_settings": "Enable Advanced Parameters",
"auto_tpi_continuous_kext": "Continuous Kext Learning"
@@ -321,6 +322,7 @@
"heater_heating_time": "Time to reach full power (minutes)",
"heater_cooling_time": "Time to cool down after stop (minutes)\n\n| Type | Heating time | Cooling time |\n| :--- | :--- | :--- |\n| Electric radiator | 5mn | 7mn |\n| Water radiator | 15mn | 20mn |\n| Underfloor heating | 30mn | 45mn |",
"auto_tpi_heating_rate": "Radiator temperature rise capacity ({unit} per hour). Leave at 0 for automatic learning (bootstrap).",
"auto_tpi_cooling_rate": "Radiator temperature cooling capacity ({unit} per hour). Leave at 0 for automatic learning (bootstrap).",
"auto_tpi_aggressiveness": "Reduction factor for learned coefficients (50-100%). Lower values result in more conservative coefficients and reduce overshoot risk.",
"auto_tpi_enable_advanced_settings": "Check to access parameter fine-tuning (Alpha, Decay, Weight).",
"auto_tpi_continuous_kext": "Check to enable continuous learning of the external coefficient (Kext) outside of AutoTPI sessions."
@@ -746,6 +748,7 @@
"data": {
"auto_tpi_learning_type": "Learning Type",
"auto_tpi_heating_rate": "Heating Rate ({unit}/h)",
"auto_tpi_cooling_rate": "Cooling Rate ({unit}/h)",
"heater_heating_time": "Heating time (min)",
"heater_cooling_time": "Cooling time (min)",
"auto_tpi_aggressiveness": "Aggressiveness",
@@ -755,6 +758,7 @@
"data_description": {
"auto_tpi_learning_type": "Choose 'Discovery' for initial startup (Weighted Average, weight 1) or 'Fine Tuning' for continuous adjustments (EWMA, Alpha 0.08).",
"auto_tpi_heating_rate": "Radiator temperature rise capacity ({unit} per hour). Leave at 0 for automatic learning (bootstrap).",
"auto_tpi_cooling_rate": "Radiator temperature cooling capacity ({unit} per hour). Leave at 0 for automatic learning (bootstrap).",
"heater_heating_time": "Time to reach full power (minutes)",
"heater_cooling_time": "Time to cool down after stop (minutes)\n\n| Type | Heating time | Cooling time |\n| :--- | :--- | :--- |\n| Electric radiator | 5mn | 7mn |\n| Water radiator | 15mn | 20mn |\n| Underfloor heating | 30mn | 45mn |",
"auto_tpi_aggressiveness": "Reduction factor for learned coefficients (50-100%). Lower values result in more conservative coefficients and reduce overshoot risk.",
@@ -948,6 +952,56 @@
"name": "Central boiler"
}
},
"sensor": {
"energy": {
"name": "Energy"
},
"mean_power_cycle": {
"name": "Mean power cycle"
},
"power_percent": {
"name": "Power percent"
},
"auto_tpi_learning": {
"name": "Auto TPI Learning State",
"state": {
"disabled": "Disabled",
"bootstrap": "Bootstrap",
"active": "Active",
"off": "Off"
}
},
"valve_open_percent": {
"name": "Valve open percent"
},
"on_time": {
"name": "On time"
},
"off_time": {
"name": "Off time"
},
"last_temperature_date": {
"name": "Last temperature date"
},
"last_external_temperature_date": {
"name": "Last external temperature date"
},
"temperature_slope": {
"name": "Temperature slope"
},
"regulated_temperature": {
"name": "Regulated temperature"
},
"ema_temperature": {
"name": "EMA temperature"
},
"nb_device_active_boiler": {
"name": "Number of active devices for boiler"
},
"total_power_active_boiler": {
"name": "Total active power for boiler"
}
},
"number": {
"frost_temp": {
"name": "Frost"
@@ -313,6 +313,7 @@
"heater_heating_time": "Temps de chauffe (min)",
"heater_cooling_time": "Temps de refroidissement (min)",
"auto_tpi_heating_rate": "Taux de chauffe ({unit}/h)",
"auto_tpi_cooling_rate": "Taux de refroidissement ({unit}/h)",
"auto_tpi_aggressiveness": "Agressivité",
"auto_tpi_enable_advanced_settings": "Activer les paramètres avancés",
"auto_tpi_continuous_kext": "Apprentissage continu de Kext"
@@ -322,6 +323,7 @@
"heater_heating_time": "Temps pour atteindre la pleine puissance (minutes)",
"heater_cooling_time": "Temps pour refroidir après arrêt (minutes)\n\n| Type | Temps de chauffe | Temps de refroidissement |\n| :--- | :--- | :--- |\n| Radiateur électrique | 5mn | 7mn |\n| Radiateur à eau | 15mn | 20mn |\n| Chauffage par le sol | 30mn | 45mn |",
"auto_tpi_heating_rate": "Capacité de montée en température du radiateur ({unit} par heure). Laisser à 0 pour l'apprentissage automatique (bootstrap).",
"auto_tpi_cooling_rate": "Capacité de descente en température du radiateur ({unit} par heure). Laisser à 0 pour l'apprentissage automatique (bootstrap).",
"auto_tpi_aggressiveness": "Facteur de réduction des coefficients appris (50-100%). Des valeurs plus basses donnent des coefficients plus conservateurs et réduisent le risque de dépassement de consigne.",
"auto_tpi_enable_advanced_settings": "Cochez pour accéder au réglage fin des paramètres (Alpha, Décroissance, Poids).",
"auto_tpi_continuous_kext": "Cochez pour activer l'ajustement continu du coefficient extérieur (Kext) hors session AutoTPI."
@@ -724,6 +726,7 @@
"data": {
"auto_tpi_learning_type": "Type d'apprentissage",
"auto_tpi_heating_rate": "Taux de chauffe ({unit}/h)",
"auto_tpi_cooling_rate": "Taux de refroidissement ({unit}/h)",
"heater_heating_time": "Temps de chauffe (min)",
"heater_cooling_time": "Temps de refroidissement (min)",
"auto_tpi_aggressiveness": "Agressivité",
@@ -733,6 +736,7 @@
"data_description": {
"auto_tpi_learning_type": "Choisissez 'Découverte' pour le démarrage initial (Moyenne pondérée, poids 1) ou 'Ajustement fin' pour des ajustements continus (EWMA, Alpha 0.08).",
"auto_tpi_heating_rate": "Capacité de montée en température du radiateur ({unit} par heure). Laisser à 0 pour l'apprentissage automatique (bootstrap).",
"auto_tpi_cooling_rate": "Capacité de descente en température du radiateur ({unit} par heure). Laisser à 0 pour l'apprentissage automatique (bootstrap).",
"heater_heating_time": "Temps pour atteindre la pleine puissance (minutes)",
"heater_cooling_time": "Temps pour refroidir après arrêt (minutes)\n\n| Type | Temps de chauffe | Temps de refroidissement |\n| :--- | :--- | :--- |\n| Radiateur électrique | 5mn | 7mn |\n| Radiateur à eau | 15mn | 20mn |\n| Chauffage par le sol | 30mn | 45mn |",
"auto_tpi_aggressiveness": "Facteur de réduction des coefficients appris (50-100%). Des valeurs plus basses donnent des coefficients plus conservateurs et réduisent le risque de dépassement de consigne.",
@@ -312,6 +312,7 @@
"heater_heating_time": "Tempo riscaldamento (min)",
"heater_cooling_time": "Tempo raffrescamento (min)",
"auto_tpi_heating_rate": "Velocità di riscaldamento ({unit}/h)",
"auto_tpi_cooling_rate": "Velocità di raffreddamento ({unit}/h)",
"auto_tpi_aggressiveness": "Aggressività",
"auto_tpi_enable_advanced_settings": "Abilita paramnetri avanzati",
"auto_tpi_continuous_kext": "Apprendimento continuo di Kext"
@@ -321,6 +322,7 @@
"heater_heating_time": "Tempo necessario per raggiungere la piena potenza (minuti)",
"heater_cooling_time": "Tempo di raffreddamento dopo l'arresto (minuti)\n\n| Tipo | Tempo di riscaldamento | Tempo di raffreddamento |\n| :--- | :--- | :--- |\n| Radiatore elettrico | 5 min | 7 min |\n| Radiatore ad acqua | 15 min | 20 min |\n| Riscaldamento a pavimento | 30 min | 45 min |",
"auto_tpi_heating_rate": "Capacità di aumento della temperatura del radiatore ({unit} all'ora). Lasciare a 0 per l'apprendimento automatico (bootstrap).",
"auto_tpi_cooling_rate": "Capacità di raffreddamento del radiatore ({unit} all'ora). Lasciare a 0 per l'apprendimento automatico (bootstrap).",
"auto_tpi_aggressiveness": "Fattore di riduzione per i coefficienti appresi (50-100%). Valori inferiori determinano coefficienti più conservativi e riducono il rischio di sovra-riscaldamento.",
"auto_tpi_enable_advanced_settings": "Controlla per accedere alla regolazione fine dei parametri (Alpha, Decadimento, Peso).",
"auto_tpi_continuous_kext": "Selezionare per attivare la regolazione continua del coefficiente esterno (Kext) al di fuori della sessione AutoTPI."
@@ -710,6 +712,7 @@
"data": {
"auto_tpi_learning_type": "Tipo di apprendimento",
"auto_tpi_heating_rate": "Velocità di riscaldamento ({unit}/h)",
"auto_tpi_cooling_rate": "Velocità di raffreddamento ({unit}/h)",
"heater_heating_time": "Tempo di riscaldamento (min)",
"heater_cooling_time": "Tempo di raffrescamento (min)",
"auto_tpi_aggressiveness": "Aggressività",
@@ -719,6 +722,7 @@
"data_description": {
"auto_tpi_learning_type": "Scegliere “Discovery” per l'avvio iniziale (media ponderata, peso 1) o “Fine Tuning” per le regolazioni continue (EWMA, Alpha 0,08).",
"auto_tpi_heating_rate": "Capacità di aumento della temperatura del radiatore ({unit} all'ora). Lasciare a 0 per l'apprendimento automatico (bootstrap).",
"auto_tpi_cooling_rate": "Capacità di raffreddamento del radiatore ({unit} all'ora). Lasciare a 0 per l'apprendimento automatico (bootstrap).",
"heater_heating_time": "Tempo necessario per raggiungere la piena potenza (minuti)",
"heater_cooling_time": "Tempo di raffreddamento dopo l'arresto (minuti)\n\n| Tipo | Tempo di riscaldamento | Tempo di raffreddamento |\n| :--- | :--- | :--- |\n| Radiatore elettrico | 5 min | 7 min |\n| Radiatore ad acqua | 15 min | 20 min |\n| Riscaldamento a pavimento | 30 min | 45 min |",
"auto_tpi_aggressiveness": "Fattore di riduzione per i coefficienti appresi (50-100%). Valori inferiori determinano coefficienti più conservativi e riducono il rischio di sovrariscaldamento.",
@@ -1087,4 +1091,4 @@
"message": "L'apprendimento automatico TPI per {name} è stato interrotto a causa di 3 errori consecutivi. Motivo: {reason}. Controllare la configurazione."
}
}
}
}
@@ -325,6 +325,14 @@
"humidity_sensor_entity_id": "Pozostaw puste, aby automatycznie użyć wykrytego czujnika."
}
},
"auto_tpi_configuration": {
"data": {
"auto_tpi_cooling_rate": "Szybkość chłodzenia ({unit}/h)"
},
"data_description": {
"auto_tpi_cooling_rate": "Zdolność chłodzenia grzejnika ({unit} na godzinę). Pozostaw 0 dla automatycznego uczenia (bootstrap)."
}
},
"heating_failure_detection": {
"title": "Wykrywanie awarii ogrzewania",
"description": "Konfiguracja funkcji wykrywania awarii ogrzewania. Wykrywa anomalie, gdy ogrzewanie jest oczekiwane, ale temperatura nie wzrasta, lub gdy ogrzewanie jest wyłączone, ale temperatura nadal rośnie.",
@@ -693,8 +701,8 @@
"heater_heating_time": "Czas nagrzewania się grzejnika (w min.)",
"heater_cooling_time": "Czas wychładzania się grzejnika (w min.)",
"auto_tpi_max_coef_int": "Maksymalna wartość współczynnika wewnętrznego",
"auto_tpi_heating_rate": "Szybkość nagrzewania (°C/h)",
"auto_tpi_cooling_rate": "Szybkość wystudzania (°C/h)"
"auto_tpi_heating_rate": "Szybkość nagrzewania ({unit}/h)",
"auto_tpi_cooling_rate": "Szybkość wystudzania ({unit}/h)"
},
"data_description": {
"auto_tpi_enable_update_config": "Automatycznie aktualizuj konfigurację o wyuczone współczynniki",
@@ -703,7 +711,8 @@
"auto_tpi_continuous_learning": "Po zakończeniu procesu nauki system będzie obliczał i aplikował w tle nowe wartości współczynników (zalecana metoda EMA).",
"heater_heating_time": "Czas osiągnięcia pełnej mocy (w min.)",
"heater_cooling_time": "Czas wychłodzenia po zatrzymaniu (w min.)",
"auto_tpi_max_coef_int": "Maksymalna dopuszczalna wartość współczynnika wewnętrznego"
"auto_tpi_max_coef_int": "Maksymalna dopuszczalna wartość współczynnika wewnętrznego",
"auto_tpi_cooling_rate": "Zdolność chłodzenia grzejnika ({unit} na godzinę). Pozostaw 0 dla automatycznego uczenia (bootstrap)."
}
},
"auto_tpi_avg_settings": {
@@ -729,7 +738,7 @@
}
},
"heating_failure_detection": {
"title": "Wykrywanie awarii ogrzewania",
"title": "Wykrywanie awarii ogrzewania - {name}",
"description": "Konfiguracja funkcji wykrywania awarii ogrzewania. Wykrywa anomalie, gdy ogrzewanie jest oczekiwane, ale temperatura nie wzrasta, lub gdy ogrzewanie jest wyłączone, ale temperatura nadal rośnie.",
"data": {
"use_heating_failure_detection_feature": "Włącz wykrywanie awarii ogrzewania",
@@ -737,7 +746,7 @@
"heating_failure_threshold": "Próg awarii ogrzewania",
"cooling_failure_threshold": "Próg awarii chłodzenia",
"heating_failure_detection_delay": "Opóźnienie wykrywania (minuty)",
"temperature_change_tolerance": "Tolerancja zmiany temperatury (°C)",
"temperature_change_tolerance": "Tolerancja zmiany temperatury ({unit})",
"failure_detection_enable_template": "Szablon włączenia wykrywania"
},
"data_description": {
@@ -307,6 +307,14 @@
"sync_entity_ids": "The list of the entities used to synchronize the internal temperature of the underlying devices. There should be one per underlying device. Should be a calibration entity if checkbox is selected or a temperature entity otherwise. Both are `number` entities."
}
},
"auto_tpi_configuration": {
"data": {
"auto_tpi_cooling_rate": "Скорость охлаждения ({unit}/ч)"
},
"data_description": {
"auto_tpi_cooling_rate": "Способность радиатора к охлаждению ({unit} в час). Оставьте 0 для автоматического обучения (bootstrap)."
}
},
"heating_failure_detection": {
"title": "Обнаружение сбоев отопления",
"description": "Настройка функции обнаружения сбоев отопления. Обнаруживает аномалии, когда ожидается нагрев, но температура не повышается, или когда отопление выключено, но температура продолжает расти.",
@@ -654,6 +662,14 @@
"sync_entity_ids": "The list of the entities used to synchronize the internal temperature of the underlying devices. There should be one per underlying device. Should be a calibration entity if checkbox is selected or a temperature entity otherwise. Both are `number` entities."
}
},
"auto_tpi_configuration": {
"data": {
"auto_tpi_cooling_rate": "Скорость охлаждения ({unit}/ч)"
},
"data_description": {
"auto_tpi_cooling_rate": "Способность радиатора к охлаждению ({unit} в час). Оставьте 0 для автоматического обучения (bootstrap)."
}
},
"heating_failure_detection": {
"title": "Обнаружение сбоев отопления",
"description": "Настройка функции обнаружения сбоев отопления. Обнаруживает аномалии, когда ожидается нагрев, но температура не повышается, или когда отопление выключено, но температура продолжает расти.",
@@ -832,4 +848,4 @@
"description": "Переводит VTherm в сезонный режим сна. Он отображается как выключенный и для over_climate с прямым управлением клапаном и over_valve отправляет необработанный запрос на открытие клапана на 100 %. Физическое открытие по-прежнему ограничивается настроенным управлением клапаном и пределами сущности number."
}
}
}
}
@@ -300,6 +300,14 @@
"max_closing_degree": "Maximálna hodnota stupňa zatvorenia. Ventil sa nikdy nezatvorí viac ako táto hodnota. Nastavte na 100 pre úplné zatvorenie ventilu"
}
},
"auto_tpi_configuration": {
"data": {
"auto_tpi_cooling_rate": "Rýchlosť chladenia ({unit}/h)"
},
"data_description": {
"auto_tpi_cooling_rate": "Schopnosť chladiča ochladzovať ({unit} za hodinu). Pre automatické učenie (bootstrap) ponechajte hodnotu 0."
}
},
"heating_failure_detection": {
"title": "Detekcia porúch vykurovania",
"description": "Konfigurácia funkcie detekcie porúch vykurovania. Detekuje anomálie, keď sa očakáva vykurovanie, ale teplota nestúpa, alebo keď je vykurovanie vypnuté, ale teplota stále stúpa.",
@@ -637,6 +645,14 @@
"max_closing_degree": "Maximálna hodnota stupňa zatvorenia. Ventil sa nikdy nezatvorí viac ako táto hodnota. Nastavte na 100 pre úplné zatvorenie ventilu"
}
},
"auto_tpi_configuration": {
"data": {
"auto_tpi_cooling_rate": "Rýchlosť chladenia ({unit}/h)"
},
"data_description": {
"auto_tpi_cooling_rate": "Schopnosť chladiča ochladzovať ({unit} za hodinu). Pre automatické učenie (bootstrap) ponechajte hodnotu 0."
}
},
"heating_failure_detection": {
"title": "Detekcia porúch vykurovania",
"description": "Konfigurácia funkcie detekcie porúch vykurovania. Detekuje anomálie, keď sa očakáva vykurovanie, ale teplota nestúpa, alebo keď je vykurovanie vypnuté, ale teplota stále stúpa.",
@@ -804,4 +820,4 @@
"description": "Prepne VTherm do sezónneho režimu spánku. Zobrazuje sa ako vypnutý a pre over_climate s priamym riadením ventilu a over_valve odošle hrubú požiadavku na otvorenie ventilu na 100 %. Fyzické otvorenie naďalej podlieha nastavenému riadeniu ventilu a limitom entity number."
}
}
}
}
@@ -311,6 +311,7 @@
"heater_heating_time": "加热时间(分钟)",
"heater_cooling_time": "冷却时间(分钟)",
"auto_tpi_heating_rate": "升温速率({unit}/h)",
"auto_tpi_cooling_rate": "冷却速率 ({unit}/小时)",
"auto_tpi_aggressiveness": "激进程度",
"auto_tpi_enable_advanced_settings": "启用高级参数",
"auto_tpi_continuous_kext": "持续学习 Kext"
@@ -320,6 +321,7 @@
"heater_heating_time": "达到满功率的时间(分钟)",
"heater_cooling_time": "停止后的冷却时间(分钟)\n\n| 类型 | 加热时间 | 冷却时间 |\n| :--- | :--- | :--- |\n| 电暖器 | 5mn | 7mn |\n| 水暖散热器 | 15mn | 20mn |\n| 地暖 | 30mn | 45mn |",
"auto_tpi_heating_rate": "散热器升温能力({unit}每小时)。保留为 0 以进行自动学习(引导程序)。",
"auto_tpi_cooling_rate": "散热器降温能力(每小时 {unit})。保留为 0 以进行自动学习(bootstrap)。",
"auto_tpi_aggressiveness": "学习系数的缩减因子 (50-100%)。较低的值会产生更保守的系数并降低超调风险。",
"auto_tpi_enable_advanced_settings": "勾选后可访问参数微调(Alpha、衰减、权重)。",
"auto_tpi_continuous_kext": "选中以启用 AutoTPI 会话之外的外部系数 (Kext) 的持续学习。"
@@ -722,6 +724,7 @@
"data": {
"auto_tpi_learning_type": "学习类型",
"auto_tpi_heating_rate": "升温速率({unit}/h)",
"auto_tpi_cooling_rate": "冷却速率 ({unit}/小时)",
"heater_heating_time": "加热时间(分钟)",
"heater_cooling_time": "冷却时间(分钟)",
"auto_tpi_aggressiveness": "激进程度",
@@ -731,6 +734,7 @@
"data_description": {
"auto_tpi_learning_type": "选择“发现”用于初始启动(加权平均,权重 1),或选择“微调”用于持续调整(EWMA,Alpha 0.08)。",
"auto_tpi_heating_rate": "散热器升温能力({unit}每小时)。保留为 0 以进行自动学习(引导程序)。",
"auto_tpi_cooling_rate": "散热器降温能力(每小时 {unit})。保留为 0 以进行自动学习(bootstrap)。",
"heater_heating_time": "达到满功率的时间(分钟)",
"heater_cooling_time": "停止后的冷却时间(分钟)\n\n| 类型 | 加热时间 | 冷却时间 |\n| :--- | :--- | :--- |\n| 电暖器 | 5mn | 7mn |\n| 水暖散热器 | 15mn | 20mn |\n| 地暖 | 30mn | 45mn |",
"auto_tpi_aggressiveness": "学习系数的缩减因子 (50-100%)。较低的值会产生更保守的系数并降低超调风险。",
@@ -1087,4 +1091,4 @@
"message": "由于连续 3 次失败,{name} 的自动 TPI 学习已停止。原因:{reason}。请检查您的配置。"
}
}
}
}
@@ -1,6 +1,7 @@
# pylint: disable=unused-argument, line-too-long, too-many-lines, broad-exception-caught
"""Underlying entities classes"""
import math
import re
from typing import Any, Dict, List, Optional, Tuple, TypeVar
from collections.abc import Callable
@@ -1456,6 +1457,8 @@ class UnderlyingValveRegulation(UnderlyingValve):
self._min_opening_degree: int = min_opening_degree
self._max_closing_degree: int = max_closing_degree
self._opening_threshold: int = opening_threshold
self._seed_attempted_targets: set[str] = set()
self._seed_diagnostics: dict[str, str] = {}
if self._min_opening_degree >= self._max_opening_degree:
self._min_opening_degree = self._opening_threshold
@@ -1542,6 +1545,122 @@ class UnderlyingValveRegulation(UnderlyingValve):
entities.append(self._closing_degree_entity_id)
self._state_manager.add_underlying_entities(entities)
async def async_startup(self) -> None:
"""Install normal listeners then run the bounded one-shot seed."""
self.startup()
await self._seed_unknown_degrees_once()
@staticmethod
def _is_finite_number(value: Any) -> bool:
"""Return whether a value is a finite numeric value, excluding bool."""
return isinstance(value, (int, float)) and not isinstance(value, bool) and math.isfinite(value)
def _target_state_kind(self, entity_id: str) -> str:
"""Classify a configured degree state for the one-shot seed."""
state = self._state_manager.get_state(entity_id)
if state is None:
return "state_absent"
if state.state == STATE_UNKNOWN:
return "unknown"
if state.state == STATE_UNAVAILABLE:
return "state_unavailable"
value = get_safe_float_value(state.state)
return "numeric" if value is not None and math.isfinite(value) else "invalid_target_state"
def _seed_pair_if_valid(self, targets: list[str]) -> tuple[int, int] | None:
"""Return the safe d=0 pair only when all pair preconditions hold."""
configured_bounds = (
self._min_opening_degree,
self._max_opening_degree,
self._max_closing_degree,
self._opening_threshold,
)
if not all(self._is_finite_number(value) for value in configured_bounds):
return None
if not (0 <= self._min_opening_degree < self._max_opening_degree <= 100 and 0 <= self._max_closing_degree <= 100 and 0 <= self._opening_threshold <= 100):
return None
opening_degree, closing_degree = OpeningClosingDegreeCalculation.calculate_opening_closing_degree(
brut_valve_open_percent=0,
min_opening_degree=self._min_opening_degree,
max_closing_degree=self._max_closing_degree,
max_opening_degree=self._max_opening_degree,
opening_threshold=self._opening_threshold,
)
if not all(isinstance(value, int) and not isinstance(value, bool) for value in (opening_degree, closing_degree)):
return None
values = {
self._opening_degree_entity_id: opening_degree,
self._closing_degree_entity_id: closing_degree,
}
for entity_id in targets:
state = self._state_manager.get_state(entity_id)
if state is None:
return None
minimum = state.attributes.get("min")
maximum = state.attributes.get("max")
if not self._is_finite_number(minimum) or not self._is_finite_number(maximum):
return None
minimum_value = float(minimum)
maximum_value = float(maximum)
if minimum_value > maximum_value:
return None
if not minimum_value <= values[entity_id] <= maximum_value:
return None
return opening_degree, closing_degree
async def _seed_unknown_degrees_once(self) -> None:
"""Seed exact unknown degree targets once without affecting readiness."""
targets = self.valve_entity_ids
state_kinds = {entity_id: self._target_state_kind(entity_id) for entity_id in targets}
invalid_targets = {entity_id: kind for entity_id, kind in state_kinds.items() if kind not in ("numeric", "unknown")}
if invalid_targets:
self._seed_diagnostics.update(invalid_targets)
_LOGGER.warning("%s - Valve-regulation seed skipped: %s", self, invalid_targets)
return
unknown_targets = [entity_id for entity_id, kind in state_kinds.items() if kind == "unknown"]
if not unknown_targets:
for entity_id in targets:
self._seed_diagnostics[entity_id] = "already_numeric"
return
pair = self._seed_pair_if_valid(targets)
if pair is None:
for entity_id in targets:
self._seed_diagnostics[entity_id] = "invalid_bounds"
_LOGGER.warning("%s - Valve-regulation seed skipped: invalid_bounds", self)
return
values = dict(zip((self._opening_degree_entity_id, self._closing_degree_entity_id), pair))
for entity_id in unknown_targets:
if entity_id in self._seed_attempted_targets:
continue
self._seed_attempted_targets.add(entity_id)
try:
await self.send_value_to_number(entity_id, values[entity_id])
except Exception: # pragma: no cover - helper errors are integration-dependent
self._seed_diagnostics[entity_id] = "service_error"
_LOGGER.warning("%s - Valve-regulation seed service_error for %s", self, entity_id)
else:
self._seed_diagnostics[entity_id] = "seed_attempted"
_LOGGER.info("%s - Valve-regulation seed_attempted for %s with value %s", self, entity_id, values[entity_id])
@property
def bootstrap_diagnostics(self) -> dict[str, Any]:
"""Return seed diagnostics without inferring target readiness."""
return {
"seed_targets": dict(self._seed_diagnostics),
"seed_attempted_targets": sorted(self._seed_attempted_targets),
}
@overrides
def remove_entity(self):
"""Remove the listener without issuing a compensating command."""
super().remove_entity()
@overrides
def _get_controlled_percent(self, raw_percent: int) -> int:
"""Keep the raw TPI percentage until ``send_percent_open``.