76 files
This commit is contained in:
@@ -9,5 +9,5 @@
|
||||
"iot_class": "local_push",
|
||||
"issue_tracker": "https://github.com/lovelylain/hass_ingress/issues",
|
||||
"single_config_entry": true,
|
||||
"version": "1.3.0"
|
||||
"version": "1.3.2"
|
||||
}
|
||||
|
||||
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -17,6 +17,12 @@ _LOGGER = get_vtherm_logger(__name__)
|
||||
class BaseFeatureManager:
|
||||
"""A base class for all feature"""
|
||||
|
||||
# Attributes that should be excluded from the recorder history. Each manager
|
||||
# exposes its custom attributes under a dedicated top-level section key; that
|
||||
# section name is declared here so the recorder skips the whole section
|
||||
# (the recorder can only filter top-level keys, not nested ones).
|
||||
unrecorded_attributes = frozenset()
|
||||
|
||||
def __init__(self, vtherm: Any, hass: HomeAssistant, name: str = None):
|
||||
"""Init of a featureManager"""
|
||||
self._vtherm = vtherm
|
||||
|
||||
@@ -85,11 +85,16 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
_attr_swing_horizontal_mode = ""
|
||||
|
||||
_entity_component_unrecorded_attributes = (
|
||||
ClimateEntity._entity_component_unrecorded_attributes.union(frozenset({"configuration", "preset_temperatures"}))
|
||||
ClimateEntity._entity_component_unrecorded_attributes.union(frozenset({"configuration", "preset_temperatures", "specific_states"}))
|
||||
.union(FeaturePresenceManager.unrecorded_attributes)
|
||||
.union(FeaturePowerManager.unrecorded_attributes)
|
||||
.union(FeatureMotionManager.unrecorded_attributes)
|
||||
.union(FeatureWindowManager.unrecorded_attributes)
|
||||
.union(FeatureSafetyManager.unrecorded_attributes)
|
||||
.union(FeatureLockManager.unrecorded_attributes)
|
||||
.union(FeatureTimedPresetManager.unrecorded_attributes)
|
||||
.union(FeatureHeatingFailureDetectionManager.unrecorded_attributes)
|
||||
.union(FeatureRepairIncorrectStateManager.unrecorded_attributes)
|
||||
)
|
||||
|
||||
##
|
||||
@@ -192,6 +197,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
self._use_central_config_temperature = False
|
||||
|
||||
self._hvac_off_reason: str | None = None
|
||||
self._hvac_mode_reason: str | None = None
|
||||
self._hvac_list: list[VThermHvacMode] = []
|
||||
self._str_hvac_list: list[str] = []
|
||||
self._temperature_reason: str | None = None
|
||||
@@ -634,6 +640,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
|
||||
# Try to get total_energy from specific_states (new format) or root level (old format)
|
||||
specific_states = old_state.attributes.get("specific_states", {})
|
||||
self._hvac_mode_reason = specific_states.get(HVAC_MODE_REASON_NAME, self._hvac_off_reason)
|
||||
old_total_energy = specific_states.get(ATTR_TOTAL_ENERGY)
|
||||
if old_total_energy is None:
|
||||
# Fallback to root level for backward compatibility
|
||||
@@ -1280,6 +1287,14 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
window detection or auto-start-stop"""
|
||||
return self._hvac_off_reason
|
||||
|
||||
@property
|
||||
def hvac_mode_reason(self) -> str | None:
|
||||
"""Returns the reason why the current hvac_mode is forced.
|
||||
Unlike hvac_off_reason, this is set whatever the forced hvac_mode is
|
||||
(off, fan_only, dry, ...) so the UI can explain why the VTherm is not
|
||||
in the requested mode."""
|
||||
return self._hvac_mode_reason
|
||||
|
||||
@property
|
||||
def temperature_reason(self) -> str | None:
|
||||
"""Returns the reason of the target temperature
|
||||
@@ -1680,6 +1695,9 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
# issue #1958 - when window_action=fan_only temporarily switches the mode to FAN_ONLY,
|
||||
# the user's intent (requested_state) is still COOL, so we must use AC presets.
|
||||
or (self.vtherm_hvac_mode == VThermHvacMode_FAN_ONLY and self.requested_state.hvac_mode == VThermHvacMode_COOL)
|
||||
# when auto-start/stop temporarily switches the mode to DRY as its stop mode,
|
||||
# the user's intent (requested_state) is still COOL, so we must use AC presets.
|
||||
or (self.vtherm_hvac_mode == VThermHvacMode_DRY and self.requested_state.hvac_mode == VThermHvacMode_COOL)
|
||||
# (self.is_over_switch and self._ac_mode)
|
||||
# or self.vtherm_hvac_mode == VThermHvacMode_COOL
|
||||
# or (self.vtherm_hvac_mode == VThermHvacMode_OFF and self.requested_state.hvac_mode == VThermHvacMode_COOL)
|
||||
@@ -1733,6 +1751,10 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
"""Set the reason of hvac_off"""
|
||||
self._hvac_off_reason = hvac_off_reason
|
||||
|
||||
def set_hvac_mode_reason(self, hvac_mode_reason: str | None):
|
||||
"""Set the reason why the current hvac_mode is forced"""
|
||||
self._hvac_mode_reason = hvac_mode_reason
|
||||
|
||||
def set_temperature_reason(self, temperature_reason: str | None):
|
||||
"""Set the reason of temperature"""
|
||||
self._temperature_reason = temperature_reason
|
||||
@@ -1823,6 +1845,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
"ema_temp": self._ema_temp,
|
||||
"temperature_slope": round(self.last_temperature_slope or 0, 3),
|
||||
"hvac_off_reason": self.hvac_off_reason,
|
||||
"hvac_mode_reason": self.hvac_mode_reason,
|
||||
ATTR_TOTAL_ENERGY: self.total_energy,
|
||||
"last_change_time_from_vtherm": (
|
||||
self._last_change_time_from_vtherm.astimezone(self._current_tz).isoformat() if self._last_change_time_from_vtherm is not None else None
|
||||
@@ -2134,7 +2157,13 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
"This thermostat does not use TPI algorithm."
|
||||
)
|
||||
|
||||
async def service_set_auto_tpi_mode(self, auto_tpi_mode: bool):
|
||||
async def service_set_auto_tpi_mode(
|
||||
self,
|
||||
auto_tpi_mode: bool,
|
||||
reinitialise: bool = True,
|
||||
allow_kint_boost_on_stagnation: bool = False,
|
||||
allow_kext_compensation_on_overshoot: bool = False,
|
||||
):
|
||||
"""Stub method for Auto TPI mode service on non-TPI thermostats.
|
||||
|
||||
This service is only available for switch/valve type thermostats that use TPI algorithm.
|
||||
|
||||
@@ -50,13 +50,14 @@ DOMAIN = "versatile_thermostat"
|
||||
|
||||
# The order is important.
|
||||
PLATFORMS: list[Platform] = [
|
||||
Platform.SELECT,
|
||||
Platform.CLIMATE,
|
||||
Platform.SENSOR,
|
||||
# Number should be after CLIMATE
|
||||
Platform.NUMBER,
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.SWITCH,
|
||||
# Select should be after CLIMATE
|
||||
Platform.SELECT,
|
||||
]
|
||||
|
||||
CONF_UNDERLYING_LIST = "underlying_entity_ids"
|
||||
@@ -244,7 +245,18 @@ TYPE_AUTO_START_STOP_LEVELS = Literal[ # pylint: disable=invalid-name
|
||||
AUTO_START_STOP_LEVEL_NONE,
|
||||
]
|
||||
|
||||
# The hvac_mode applied when the auto-start/stop feature detects a stop condition
|
||||
AUTO_START_STOP_STOP_MODE_OFF = str(VThermHvacMode_OFF)
|
||||
AUTO_START_STOP_STOP_MODE_FAN_ONLY = str(VThermHvacMode_FAN_ONLY)
|
||||
AUTO_START_STOP_STOP_MODE_DRY = str(VThermHvacMode_DRY)
|
||||
AUTO_START_STOP_STOP_MODES = [
|
||||
AUTO_START_STOP_STOP_MODE_OFF,
|
||||
AUTO_START_STOP_STOP_MODE_FAN_ONLY,
|
||||
AUTO_START_STOP_STOP_MODE_DRY,
|
||||
]
|
||||
|
||||
HVAC_OFF_REASON_NAME = "hvac_off_reason"
|
||||
HVAC_MODE_REASON_NAME = "hvac_mode_reason"
|
||||
HVAC_OFF_REASON_MANUAL = "hvac_off_manual"
|
||||
HVAC_OFF_REASON_AUTO_START_STOP = "hvac_off_auto_start_stop"
|
||||
HVAC_OFF_REASON_WINDOW_DETECTION = "hvac_off_window_detection"
|
||||
@@ -255,6 +267,15 @@ HVAC_OFF_REASONS = Literal[ # pylint: disable=invalid-name
|
||||
HVAC_OFF_REASON_MANUAL, HVAC_OFF_REASON_AUTO_START_STOP, HVAC_OFF_REASON_WINDOW_DETECTION, HVAC_OFF_REASON_SLEEP_MODE, HVAC_OFF_REASON_SAFETY
|
||||
]
|
||||
|
||||
# The hvac_mode_reason set when the auto-start/stop feature applies a non-off stop mode
|
||||
HVAC_MODE_REASON_AUTO_START_STOP_FAN_ONLY = "hvac_fan_only_auto_start_stop"
|
||||
HVAC_MODE_REASON_AUTO_START_STOP_DRY = "hvac_dry_auto_start_stop"
|
||||
# Maps the stop mode applied by the auto-start/stop feature to the related hvac_mode_reason
|
||||
AUTO_START_STOP_HVAC_MODE_REASONS = {
|
||||
AUTO_START_STOP_STOP_MODE_FAN_ONLY: HVAC_MODE_REASON_AUTO_START_STOP_FAN_ONLY,
|
||||
AUTO_START_STOP_STOP_MODE_DRY: HVAC_MODE_REASON_AUTO_START_STOP_DRY,
|
||||
}
|
||||
|
||||
DEFAULT_SHORT_EMA_PARAMS = {
|
||||
"max_alpha": 0.5,
|
||||
# In sec
|
||||
@@ -502,7 +523,7 @@ ATTR_TOTAL_ENERGY = "total_energy"
|
||||
ATTR_MEAN_POWER_CYCLE = "mean_cycle_power"
|
||||
|
||||
AUTO_FAN_DTEMP_THRESHOLD = 2
|
||||
AUTO_FAN_DEACTIVATED_MODES = ["mute", "quiet", "low", "quiet", "1", "auto"]
|
||||
AUTO_FAN_DEACTIVATED_MODES = ["mute", "quiet", "low", "quiet", "1", "one", "speed_1", "auto"]
|
||||
|
||||
CENTRAL_CONFIG_NAME = "Central configuration"
|
||||
|
||||
|
||||
@@ -32,12 +32,8 @@ class FeatureAutoStartStopManager(BaseFeatureManager):
|
||||
|
||||
unrecorded_attributes = frozenset(
|
||||
{
|
||||
"auto_start_stop_level",
|
||||
"auto_start_stop_dtmin",
|
||||
"auto_start_stop_enable",
|
||||
"auto_start_stop_accumulated_error",
|
||||
"auto_start_stop_accumulated_error_threshold",
|
||||
"auto_start_stop_last_switch_date",
|
||||
"is_auto_start_stop_configured",
|
||||
"auto_start_stop_manager",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -50,6 +46,7 @@ class FeatureAutoStartStopManager(BaseFeatureManager):
|
||||
self._is_configured: bool = False
|
||||
self._is_auto_start_stop_enabled: bool = False
|
||||
self._is_auto_stop_detected: bool = False
|
||||
self._stop_mode: str = AUTO_START_STOP_STOP_MODE_OFF
|
||||
|
||||
@overrides
|
||||
def post_init(self, entry_infos: ConfigData):
|
||||
@@ -126,7 +123,7 @@ class FeatureAutoStartStopManager(BaseFeatureManager):
|
||||
"type": "stop",
|
||||
"name": self.name,
|
||||
"cause": "Auto stop conditions reached",
|
||||
"hvac_mode": str(VThermHvacMode_OFF),
|
||||
"hvac_mode": str(self.stop_mode),
|
||||
"saved_hvac_mode": str(self._vtherm.requested_state.hvac_mode),
|
||||
"target_temperature": self._vtherm.target_temperature,
|
||||
"current_temperature": self._vtherm.current_temperature,
|
||||
@@ -207,6 +204,25 @@ class FeatureAutoStartStopManager(BaseFeatureManager):
|
||||
|
||||
self._vtherm.update_custom_attributes()
|
||||
|
||||
async def set_auto_start_stop_stop_mode(self, stop_mode: str):
|
||||
"""Set the hvac_mode to apply when a stop is detected (off/fan_only/dry).
|
||||
|
||||
If a stop is currently active, the new mode is applied immediately
|
||||
while the stop state is kept."""
|
||||
if self._stop_mode == stop_mode:
|
||||
return
|
||||
|
||||
write_event_log(_LOGGER, self._vtherm, f"Auto start/stop stop mode changed from {self._stop_mode} to {stop_mode}")
|
||||
self._stop_mode = stop_mode
|
||||
|
||||
# If a stop is currently active, re-evaluate the state so the hvac_mode
|
||||
# reflects the new choice immediately while keeping the stop state.
|
||||
if self._is_auto_stop_detected:
|
||||
self._vtherm.requested_state.force_changed()
|
||||
await self._vtherm.update_states(force=True)
|
||||
|
||||
self._vtherm.update_custom_attributes()
|
||||
|
||||
@callback
|
||||
@overrides
|
||||
def restore_state(self, old_state) -> None:
|
||||
@@ -272,6 +288,7 @@ class FeatureAutoStartStopManager(BaseFeatureManager):
|
||||
"auto_start_stop_accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
|
||||
"auto_start_stop_last_switch_date": self._auto_start_stop_algo.last_switch_date,
|
||||
"is_auto_stop_detected": self.is_auto_stop_detected,
|
||||
"auto_start_stop_stop_mode": self._stop_mode,
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -302,6 +319,11 @@ class FeatureAutoStartStopManager(BaseFeatureManager):
|
||||
"""Returns the auto_start_stop_enable"""
|
||||
return self._is_auto_start_stop_enabled
|
||||
|
||||
@property
|
||||
def stop_mode(self) -> VThermHvacMode:
|
||||
"""Return the hvac_mode to apply when a stop is detected (off/fan_only/dry)"""
|
||||
return VThermHvacMode(self._stop_mode)
|
||||
|
||||
@property
|
||||
def is_auto_stopped(self) -> bool:
|
||||
"""Returns the is vtherm is stopped and reason is AUTO_START_STOP"""
|
||||
|
||||
@@ -54,12 +54,8 @@ class FeatureHeatingFailureDetectionManager(BaseFeatureManager):
|
||||
|
||||
unrecorded_attributes = frozenset(
|
||||
{
|
||||
"heating_failure_threshold",
|
||||
"cooling_failure_threshold",
|
||||
"heating_failure_detection_delay",
|
||||
"temperature_change_tolerance",
|
||||
"is_heating_failure_detection_configured",
|
||||
"failure_detection_enable_template",
|
||||
"heating_failure_detection_manager",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -21,6 +21,14 @@ _LOGGER = get_vtherm_logger(__name__)
|
||||
|
||||
class FeatureLockManager(BaseFeatureManager):
|
||||
""" The implementation of the Lock Feature Manager for Versatile Thermostat """
|
||||
|
||||
unrecorded_attributes = frozenset(
|
||||
{
|
||||
"is_lock_configured",
|
||||
"lock_manager",
|
||||
}
|
||||
)
|
||||
|
||||
def __init__(self, vtherm: Any, hass: HomeAssistant):
|
||||
"""Initialize the FeatureLockManager."""
|
||||
super().__init__(vtherm, hass)
|
||||
|
||||
@@ -42,12 +42,8 @@ class FeatureMotionManager(BaseFeatureManager):
|
||||
|
||||
unrecorded_attributes = frozenset(
|
||||
{
|
||||
"motion_sensor_entity_id",
|
||||
"is_motion_configured",
|
||||
"motion_delay_sec",
|
||||
"motion_off_delay_sec",
|
||||
"motion_preset",
|
||||
"no_motion_preset",
|
||||
"motion_manager",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -31,13 +31,8 @@ class FeaturePowerManager(BaseFeatureManager):
|
||||
|
||||
unrecorded_attributes = frozenset(
|
||||
{
|
||||
"power_sensor_entity_id",
|
||||
"max_power_sensor_entity_id",
|
||||
"is_power_configured",
|
||||
"device_power",
|
||||
"power_temp",
|
||||
"current_power",
|
||||
"current_max_power",
|
||||
"power_manager",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -38,8 +38,8 @@ class FeaturePresenceManager(BaseFeatureManager):
|
||||
|
||||
unrecorded_attributes = frozenset(
|
||||
{
|
||||
"presence_sensor_entity_id",
|
||||
"is_presence_configured",
|
||||
"presence_manager",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ class FeatureRepairIncorrectStateManager(BaseFeatureManager):
|
||||
unrecorded_attributes = frozenset(
|
||||
{
|
||||
"is_repair_incorrect_state_configured",
|
||||
"repair_incorrect_state_manager",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -32,10 +32,8 @@ class FeatureSafetyManager(BaseFeatureManager):
|
||||
|
||||
unrecorded_attributes = frozenset(
|
||||
{
|
||||
"safety_delay_min",
|
||||
"safety_min_on_percent",
|
||||
"safety_default_on_percent",
|
||||
"is_safety_configured",
|
||||
"safety_manager",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -43,15 +43,8 @@ class FeatureWindowManager(BaseFeatureManager):
|
||||
|
||||
unrecorded_attributes = frozenset(
|
||||
{
|
||||
"window_sensor_entity_id",
|
||||
"is_window_configured",
|
||||
"window_delay_sec",
|
||||
"window_off_delay_sec",
|
||||
"window_auto_configured",
|
||||
"window_auto_open_threshold",
|
||||
"window_auto_close_threshold",
|
||||
"window_auto_max_duration",
|
||||
"window_action",
|
||||
"window_manager",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -134,7 +127,8 @@ class FeatureWindowManager(BaseFeatureManager):
|
||||
|
||||
# Try to get last window bypass state
|
||||
old_state = await self._vtherm.async_get_last_state()
|
||||
self._is_window_bypass = old_state is not None and hasattr(old_state, "attributes") and old_state.attributes.get("is_window_bypass") is True
|
||||
old_attributes = getattr(old_state, "attributes", None) or {}
|
||||
self._is_window_bypass = (old_attributes.get("window_manager") or {}).get("is_window_bypass") is True
|
||||
|
||||
if self._is_configured:
|
||||
self.stop_listening()
|
||||
|
||||
@@ -22,6 +22,6 @@
|
||||
"vtherm_api>=0.3.0"
|
||||
],
|
||||
"ssdp": [],
|
||||
"version": "10.0.2",
|
||||
"version": "10.1.0",
|
||||
"zeroconf": []
|
||||
}
|
||||
|
||||
@@ -86,8 +86,11 @@ class PITemperatureRegulator:
|
||||
# Calculate the sum of error (I)
|
||||
# Discussion #384. Finally don't reset the accumulated error but smoothly reset it if the sign is inversed
|
||||
# If the error have change its sign, reset smoothly the accumulated error
|
||||
# The divisor is clamped so that a fractional cycle (time_delta < 0.5), which happens when
|
||||
# the regulation is triggered twice in quick succession (e.g. repeated target changes),
|
||||
# can never amplify the accumulated error instead of decaying it.
|
||||
if self.overheat_protection and error * self.accumulated_error < 0:
|
||||
self.accumulated_error = self.accumulated_error / (2.0 * time_delta)
|
||||
self.accumulated_error = self.accumulated_error / (2.0 * max(time_delta, 0.5))
|
||||
|
||||
self.accumulated_error += error * time_delta
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
import logging
|
||||
from vtherm_api.log_collector import get_vtherm_logger
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback, Event
|
||||
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.components.select import SelectEntity
|
||||
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@@ -18,14 +19,22 @@ from custom_components.versatile_thermostat.base_thermostat import (
|
||||
|
||||
from custom_components.versatile_thermostat.vtherm_central_api import VersatileThermostatAPI
|
||||
|
||||
from .base_entity import VersatileThermostatBaseEntity
|
||||
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
DEVICE_MANUFACTURER,
|
||||
CONF_NAME,
|
||||
CONF_THERMOSTAT_TYPE,
|
||||
CONF_THERMOSTAT_CENTRAL_CONFIG,
|
||||
CONF_THERMOSTAT_CLIMATE,
|
||||
CONF_USE_AUTO_START_STOP_FEATURE,
|
||||
CENTRAL_MODE_AUTO,
|
||||
CENTRAL_MODES,
|
||||
AUTO_START_STOP_STOP_MODE_OFF,
|
||||
AUTO_START_STOP_STOP_MODE_FAN_ONLY,
|
||||
AUTO_START_STOP_STOP_MODE_DRY,
|
||||
AUTO_START_STOP_STOP_MODES,
|
||||
overrides,
|
||||
)
|
||||
from .commons import write_event_log
|
||||
@@ -44,14 +53,16 @@ async def async_setup_entry(
|
||||
_LOGGER.debug("%s - Calling async_setup_entry entry=%s, data=%s", name, entry.entry_id, entry.data)
|
||||
vt_type = entry.data.get(CONF_THERMOSTAT_TYPE)
|
||||
|
||||
if vt_type != CONF_THERMOSTAT_CENTRAL_CONFIG:
|
||||
return
|
||||
entities = []
|
||||
|
||||
entities = [
|
||||
CentralModeSelect(hass, unique_id, name, entry.data),
|
||||
]
|
||||
if vt_type == CONF_THERMOSTAT_CENTRAL_CONFIG:
|
||||
entities.append(CentralModeSelect(hass, unique_id, name, entry.data))
|
||||
elif vt_type == CONF_THERMOSTAT_CLIMATE:
|
||||
if entry.data.get(CONF_USE_AUTO_START_STOP_FEATURE) is True:
|
||||
entities.append(AutoStartStopStopModeSelect(hass, unique_id, name, entry.data))
|
||||
|
||||
async_add_entities(entities, True)
|
||||
if entities:
|
||||
async_add_entities(entities, True)
|
||||
|
||||
|
||||
class CentralModeSelect(SelectEntity, RestoreEntity):
|
||||
@@ -124,3 +135,111 @@ class CentralModeSelect(SelectEntity, RestoreEntity):
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"VersatileThermostat-{self.name}"
|
||||
|
||||
|
||||
class AutoStartStopStopModeSelect(
|
||||
VersatileThermostatBaseEntity, SelectEntity, RestoreEntity
|
||||
):
|
||||
"""Representation of the hvac_mode applied when the auto-start/stop
|
||||
feature detects a stop condition (off, fan_only or dry)."""
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, unique_id: str, name: str, entry_infos: ConfigData
|
||||
):
|
||||
"""Initialize the auto-start/stop stop mode select"""
|
||||
super().__init__(hass, unique_id, name)
|
||||
self._attr_name = "Auto start/stop stop mode"
|
||||
self._attr_unique_id = f"{self._device_name}_auto_start_stop_stop_mode"
|
||||
self._attr_translation_key = "auto_start_stop_stop_mode"
|
||||
self._attr_entity_category = EntityCategory.CONFIG
|
||||
self._attr_current_option = AUTO_START_STOP_STOP_MODE_OFF
|
||||
|
||||
@property
|
||||
def icon(self) -> str | None:
|
||||
"""The icon"""
|
||||
return "mdi:hvac"
|
||||
|
||||
@property
|
||||
def options(self) -> list[str]:
|
||||
"""The available options, computed from the underlying supported hvac_modes"""
|
||||
return self._build_options()
|
||||
|
||||
def _build_options(self) -> list[str]:
|
||||
"""Build the available options from the underlying supported hvac_modes.
|
||||
fan_only and dry are only proposed if the underlying supports them."""
|
||||
options = [AUTO_START_STOP_STOP_MODE_OFF]
|
||||
climate = self.my_climate
|
||||
if climate is not None:
|
||||
hvac_modes = climate.hvac_modes
|
||||
if AUTO_START_STOP_STOP_MODE_FAN_ONLY in hvac_modes:
|
||||
options.append(AUTO_START_STOP_STOP_MODE_FAN_ONLY)
|
||||
if AUTO_START_STOP_STOP_MODE_DRY in hvac_modes:
|
||||
options.append(AUTO_START_STOP_STOP_MODE_DRY)
|
||||
return options
|
||||
|
||||
@callback
|
||||
def my_climate_is_initialized(self):
|
||||
"""Called when the associated climate is resolved -> refresh the options.
|
||||
The restored option is only validated once the VTherm is fully
|
||||
initialized (its underlying hvac_modes are available)."""
|
||||
|
||||
self._refresh_current_option()
|
||||
self.hass.create_task(self.update_my_state_and_vtherm())
|
||||
|
||||
def _refresh_current_option(self):
|
||||
"""Reset the current option to off only once the VTherm is fully
|
||||
initialized and its underlying does not support the current option.
|
||||
While the VTherm is not initialized, the restored option is kept as is
|
||||
to avoid discarding it before the underlying hvac_modes are available."""
|
||||
climate = self.my_climate
|
||||
if climate is None or not climate.is_initialized:
|
||||
return
|
||||
if self._attr_current_option not in self.options:
|
||||
self._attr_current_option = AUTO_START_STOP_STOP_MODE_OFF
|
||||
|
||||
@overrides
|
||||
async def async_my_climate_changed(self, event: Event = None):
|
||||
"""Called when my climate changes -> refresh the available options.
|
||||
The underlying supported hvac_modes may become available only after
|
||||
the VTherm has adopted them, so the options must be recomputed."""
|
||||
if self.my_climate is None:
|
||||
return
|
||||
self._refresh_current_option()
|
||||
self.async_write_ha_state()
|
||||
|
||||
@overrides
|
||||
async def async_added_to_hass(self):
|
||||
# Restore the persisted value before looking for the climate so that
|
||||
# my_climate_is_initialized validates the options against it.
|
||||
last_state = await self.async_get_last_state()
|
||||
if last_state is not None and last_state.state in AUTO_START_STOP_STOP_MODES:
|
||||
self._attr_current_option = last_state.state
|
||||
|
||||
await super().async_added_to_hass()
|
||||
|
||||
await self.update_my_state_and_vtherm()
|
||||
|
||||
async def update_my_state_and_vtherm(self):
|
||||
"""Update the stop mode in my VTherm auto-start/stop manager"""
|
||||
self.async_write_ha_state()
|
||||
if (
|
||||
self.my_climate is not None
|
||||
and self.my_climate.auto_start_stop_manager is not None
|
||||
):
|
||||
await self.my_climate.auto_start_stop_manager.set_auto_start_stop_stop_mode(self._attr_current_option)
|
||||
|
||||
@overrides
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Change the selected option."""
|
||||
if option == self._attr_current_option:
|
||||
return
|
||||
|
||||
if option in self.options:
|
||||
write_event_log(_LOGGER, self, f"Auto start/stop stop mode is being changed from {self._attr_current_option} to {option}")
|
||||
self._attr_current_option = option
|
||||
await self.update_my_state_and_vtherm()
|
||||
|
||||
@overrides
|
||||
def select_option(self, option: str) -> None:
|
||||
"""Change the selected option"""
|
||||
self.hass.create_task(self.async_select_option(option))
|
||||
|
||||
@@ -13,6 +13,7 @@ from .const import (
|
||||
HVAC_OFF_REASON_AUTO_START_STOP,
|
||||
HVAC_OFF_REASON_SLEEP_MODE,
|
||||
HVAC_OFF_REASON_CENTRAL_MODE,
|
||||
AUTO_START_STOP_HVAC_MODE_REASONS,
|
||||
CONF_WINDOW_ECO_TEMP,
|
||||
CONF_WINDOW_FAN_ONLY,
|
||||
CONF_WINDOW_FROST_TEMP,
|
||||
@@ -112,13 +113,16 @@ class StateManager:
|
||||
if vtherm.last_central_mode == CENTRAL_MODE_STOPPED:
|
||||
self._current_state.set_hvac_mode(VThermHvacMode_OFF)
|
||||
vtherm.set_hvac_off_reason(HVAC_OFF_REASON_CENTRAL_MODE)
|
||||
vtherm.set_hvac_mode_reason(HVAC_OFF_REASON_CENTRAL_MODE)
|
||||
|
||||
elif vtherm.safety_manager.is_safety_detected and (vtherm.is_over_climate or vtherm.safety_manager.safety_default_on_percent <= 0.0):
|
||||
self._current_state.set_hvac_mode(VThermHvacMode_OFF)
|
||||
vtherm.set_hvac_off_reason(HVAC_OFF_REASON_SAFETY)
|
||||
vtherm.set_hvac_mode_reason(HVAC_OFF_REASON_SAFETY)
|
||||
|
||||
# then check if window is open
|
||||
elif vtherm.window_manager.is_window_detected and self._requested_state.hvac_mode != VThermHvacMode_OFF:
|
||||
vtherm.set_hvac_mode_reason(HVAC_OFF_REASON_WINDOW_DETECTION)
|
||||
if vtherm.window_manager.window_action == CONF_WINDOW_FAN_ONLY and VThermHvacMode_FAN_ONLY in vtherm.vtherm_hvac_modes:
|
||||
self._current_state.set_hvac_mode(VThermHvacMode_FAN_ONLY)
|
||||
elif vtherm.window_manager.window_action == CONF_WINDOW_TURN_OFF or (
|
||||
@@ -128,10 +132,17 @@ class StateManager:
|
||||
vtherm.set_hvac_off_reason(HVAC_OFF_REASON_WINDOW_DETECTION)
|
||||
|
||||
elif vtherm.auto_start_stop_manager and vtherm.auto_start_stop_manager.is_auto_stop_detected and self._requested_state.hvac_mode != VThermHvacMode_OFF:
|
||||
self._current_state.set_hvac_mode(VThermHvacMode_OFF)
|
||||
vtherm.set_hvac_off_reason(HVAC_OFF_REASON_AUTO_START_STOP)
|
||||
stop_mode = vtherm.auto_start_stop_manager.stop_mode
|
||||
if stop_mode != VThermHvacMode_OFF and stop_mode in vtherm.vtherm_hvac_modes:
|
||||
self._current_state.set_hvac_mode(stop_mode)
|
||||
vtherm.set_hvac_mode_reason(AUTO_START_STOP_HVAC_MODE_REASONS.get(str(stop_mode), HVAC_OFF_REASON_AUTO_START_STOP))
|
||||
else:
|
||||
self._current_state.set_hvac_mode(VThermHvacMode_OFF)
|
||||
vtherm.set_hvac_off_reason(HVAC_OFF_REASON_AUTO_START_STOP)
|
||||
vtherm.set_hvac_mode_reason(HVAC_OFF_REASON_AUTO_START_STOP)
|
||||
|
||||
elif vtherm.last_central_mode == CENTRAL_MODE_COOL_ONLY and self._requested_state.hvac_mode != VThermHvacMode_OFF:
|
||||
vtherm.set_hvac_mode_reason(HVAC_OFF_REASON_CENTRAL_MODE)
|
||||
if VThermHvacMode_COOL in vtherm.vtherm_hvac_modes:
|
||||
self._current_state.set_hvac_mode(VThermHvacMode_COOL)
|
||||
else:
|
||||
@@ -139,6 +150,7 @@ class StateManager:
|
||||
self._current_state.set_hvac_mode(VThermHvacMode_OFF)
|
||||
|
||||
elif vtherm.last_central_mode == CENTRAL_MODE_HEAT_ONLY and self._requested_state.hvac_mode != VThermHvacMode_OFF:
|
||||
vtherm.set_hvac_mode_reason(HVAC_OFF_REASON_CENTRAL_MODE)
|
||||
if VThermHvacMode_HEAT in vtherm.vtherm_hvac_modes:
|
||||
self._current_state.set_hvac_mode(VThermHvacMode_HEAT)
|
||||
else:
|
||||
@@ -146,6 +158,7 @@ class StateManager:
|
||||
self._current_state.set_hvac_mode(VThermHvacMode_OFF)
|
||||
|
||||
elif vtherm.last_central_mode == CENTRAL_MODE_FROST_PROTECTION and self._requested_state.hvac_mode != VThermHvacMode_OFF:
|
||||
vtherm.set_hvac_mode_reason(HVAC_OFF_REASON_CENTRAL_MODE)
|
||||
preset_modes = vtherm.vtherm_preset_modes
|
||||
if preset_modes is None or VThermPreset.FROST not in preset_modes or VThermHvacMode_HEAT not in vtherm.vtherm_hvac_modes:
|
||||
self._current_state.set_hvac_mode(VThermHvacMode_OFF)
|
||||
@@ -157,7 +170,11 @@ class StateManager:
|
||||
else:
|
||||
if self._current_state.hvac_mode == VThermHvacMode_OFF and self._requested_state.hvac_mode == VThermHvacMode_OFF:
|
||||
_LOGGER.info("%s - already in OFF. Change the reason to MANUAL", vtherm)
|
||||
vtherm.set_hvac_off_reason(HVAC_OFF_REASON_MANUAL if not vtherm.is_sleeping else HVAC_OFF_REASON_SLEEP_MODE)
|
||||
reason = HVAC_OFF_REASON_MANUAL if not vtherm.is_sleeping else HVAC_OFF_REASON_SLEEP_MODE
|
||||
vtherm.set_hvac_off_reason(reason)
|
||||
vtherm.set_hvac_mode_reason(reason)
|
||||
else:
|
||||
vtherm.set_hvac_mode_reason(None)
|
||||
|
||||
self._current_state.set_hvac_mode(self._requested_state.hvac_mode)
|
||||
|
||||
@@ -166,6 +183,7 @@ class StateManager:
|
||||
vtherm.set_hvac_off_reason(None)
|
||||
elif self._current_state.hvac_mode == VThermHvacMode_SLEEP:
|
||||
vtherm.set_hvac_off_reason(HVAC_OFF_REASON_SLEEP_MODE)
|
||||
vtherm.set_hvac_mode_reason(HVAC_OFF_REASON_SLEEP_MODE)
|
||||
|
||||
return self._current_state.is_hvac_mode_changed
|
||||
|
||||
|
||||
@@ -933,6 +933,16 @@
|
||||
"boost_ac_away_temp": {
|
||||
"name": "Boost ac away"
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"auto_start_stop_stop_mode": {
|
||||
"name": "Auto start/stop stop mode",
|
||||
"state": {
|
||||
"off": "Off",
|
||||
"fan_only": "Fan only",
|
||||
"dry": "Dry"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
|
||||
@@ -174,6 +174,29 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
||||
)
|
||||
return
|
||||
|
||||
if self.vtherm_hvac_mode not in (VThermHvacMode_HEAT, VThermHvacMode_COOL):
|
||||
_LOGGER.debug(
|
||||
"%s - auto-regulation is disabled cause VTherm hvac_mode is %s (not heat nor cool). Sending the raw target temperature",
|
||||
self,
|
||||
self.vtherm_hvac_mode,
|
||||
)
|
||||
# Outside of heat/cool the device is active but must receive the original
|
||||
# (non regulated) target temperature. Force the regulated temperature to the
|
||||
# target temperature and send it to all underlyings only when it differs from
|
||||
# the last sent value, to avoid resending the same setpoint on each cycle.
|
||||
self._regulated_target_temp = self.target_temperature
|
||||
for under in self._underlyings:
|
||||
if under.last_sent_temperature == self.target_temperature:
|
||||
continue
|
||||
await under.set_temperature(
|
||||
self.target_temperature,
|
||||
self._attr_max_temp,
|
||||
self._attr_min_temp,
|
||||
)
|
||||
# Reset the timer of last regulation change to avoid time delta too high
|
||||
self._last_regulation_change = self.now
|
||||
return
|
||||
|
||||
_LOGGER.info(
|
||||
"%s - Calling ThermostatClimate._send_regulated_temperature force=%s",
|
||||
self,
|
||||
@@ -416,8 +439,10 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
if not self._regulation_algo:
|
||||
# A default empty algo (which does nothing)
|
||||
if self._auto_regulation_mode == CONF_AUTO_REGULATION_NONE or not self._regulation_algo:
|
||||
# A default empty algo (which does nothing). It must also replace any
|
||||
# previously active algo when switching to None at runtime, else the
|
||||
# old regulator keeps sending regulated setpoints to the underlyings.
|
||||
self._regulation_algo = PITemperatureRegulator(self.target_temperature, 0, 0, 0, 0, 0, True)
|
||||
|
||||
def choose_auto_fan_mode(self, auto_fan_mode: str):
|
||||
@@ -440,8 +465,8 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
||||
return None
|
||||
|
||||
def determine_fan_mode_contains_speed(fan_modes: list[str]) -> bool:
|
||||
"""Determine if the fan_modes contains speed modes by searching for the keywords "low"/"1"."""
|
||||
for val in ["low", "1"]:
|
||||
"""Determine if the fan_modes contains speed modes by searching for the keywords "low"/"1"/"one"/"speed_1"."""
|
||||
for val in ["low", "1", "one", "speed_1"]:
|
||||
if find_fan_mode(fan_modes, val):
|
||||
return True
|
||||
return False
|
||||
@@ -453,6 +478,10 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
||||
index = speed_modes.index("low")
|
||||
elif "1" in speed_modes:
|
||||
index = speed_modes.index("1")
|
||||
elif "one" in speed_modes:
|
||||
index = speed_modes.index("one")
|
||||
elif "speed_1" in speed_modes:
|
||||
index = speed_modes.index("speed_1")
|
||||
|
||||
if index > -1 and index >= len(speed_modes) / 2:
|
||||
speed_modes.reverse()
|
||||
|
||||
@@ -300,7 +300,8 @@
|
||||
"heater_cooling_time": "Auskühlzeit (min)",
|
||||
"auto_tpi_heating_rate": "Aufheizgeschwindigkeit ({unit}/h)",
|
||||
"auto_tpi_aggressiveness": "Aggressivität",
|
||||
"auto_tpi_enable_advanced_settings": "Erweiterte Einstellungen aktivieren"
|
||||
"auto_tpi_enable_advanced_settings": "Erweiterte Einstellungen aktivieren",
|
||||
"auto_tpi_continuous_kext": "Kontinuierliches Kext-lernen"
|
||||
},
|
||||
"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).",
|
||||
@@ -308,7 +309,8 @@
|
||||
"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_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": "Aktivieren Sie diese Option, um die Feinabstimmung der Parameter (Alpha, Abklingen, Gewicht) vorzunehmen."
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"auto_tpi_avg_settings": {
|
||||
@@ -326,11 +328,13 @@
|
||||
"description": "Parameter für die EWMA-Methode.\n\nEmpfehlungen :\n| Situation | Alpha (ema_alpha) | Abklingrate (ema_decay_rate) |\n| :--- | :--- | :--- |\n| Lernstart | 0.15 | 0.08 |\n| Lernabschluß | 0.08 | 0.12 |\n| Kontinuierliches Lernen | 0.05 | 0.02 |",
|
||||
"data": {
|
||||
"auto_tpi_ema_alpha": "Alpha",
|
||||
"auto_tpi_ema_decay_rate": "Rückgangsrate"
|
||||
"auto_tpi_ema_decay_rate": "Rückgangsrate",
|
||||
"auto_tpi_continuous_kext_alpha": "Continuous Kext Alpha"
|
||||
},
|
||||
"data_description": {
|
||||
"auto_tpi_ema_alpha": "Glättungsfaktor (0-1). Höher = schnellere Anpassung",
|
||||
"auto_tpi_ema_decay_rate": "Rate, mit der Alpha im Laufe der Zeit abnimmt (Stabilisierung)"
|
||||
"auto_tpi_ema_decay_rate": "Rate, mit der Alpha im Laufe der Zeit abnimmt (Stabilisierung)",
|
||||
"auto_tpi_continuous_kext_alpha": "Smoothing factor for continuous Kext learning (Alpha). Default 0.04 (approx 3-5 days adaptation)."
|
||||
}
|
||||
},
|
||||
"sync_device_internal_temp": {
|
||||
@@ -698,7 +702,8 @@
|
||||
"heater_heating_time": "Aufheizzeit (min)",
|
||||
"heater_cooling_time": "Abkühlzeit (min)",
|
||||
"auto_tpi_aggressiveness": "Aggressivität",
|
||||
"auto_tpi_enable_advanced_settings": "Erweiterte Einstellungen aktivieren"
|
||||
"auto_tpi_enable_advanced_settings": "Erweiterte Einstellungen aktivieren",
|
||||
"auto_tpi_continuous_kext": "Kontinuierliches Kext-lernen"
|
||||
},
|
||||
"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).",
|
||||
@@ -706,7 +711,8 @@
|
||||
"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.",
|
||||
"auto_tpi_enable_advanced_settings": "Kontrollkästchen aktivieren, um die Parameter des ausgewählten Algorithmus zu ändern."
|
||||
"auto_tpi_enable_advanced_settings": "Kontrollkästchen aktivieren, um die Parameter des ausgewählten Algorithmus zu ändern.",
|
||||
"auto_tpi_continuous_kext": "Kontrollkästchen aktivieren, um das kontinuierliche Lernen des externen Koeffizienten (Kext) außerhalb von AutoTPI-Sitzungen zu ermöglichen."
|
||||
}
|
||||
},
|
||||
"auto_tpi_avg_settings": {
|
||||
@@ -724,11 +730,13 @@
|
||||
"description": "Parameter für die EWMA-Methode.\n\nEmpfehlungen:\n| Situation | Alpha (ema_alpha) | Abklingrate (ema_decay_rate) |\n| :--- | :--- | :--- |\n| Anfängliches Lernen | 0,15 | 0,08 |\n| Endgültiges Lernen | 0,08 | 0,12 |\n| Kontinuierliches Lernen | 0,05 | 0,02 |",
|
||||
"data": {
|
||||
"auto_tpi_ema_alpha": "Alpha",
|
||||
"auto_tpi_ema_decay_rate": "Rückgangsrate"
|
||||
"auto_tpi_ema_decay_rate": "Rückgangsrate",
|
||||
"auto_tpi_continuous_kext_alpha": "Kontinuierliches Kext Alpha"
|
||||
},
|
||||
"data_description": {
|
||||
"auto_tpi_ema_alpha": "Glättungsfaktor (0-1). Höher = schnellere Anpassung",
|
||||
"auto_tpi_ema_decay_rate": "Rate, mit der Alpha im Laufe der Zeit abnimmt (Stabilisierung)"
|
||||
"auto_tpi_ema_decay_rate": "Rate, mit der Alpha im Laufe der Zeit abnimmt (Stabilisierung)",
|
||||
"auto_tpi_continuous_kext_alpha": "Glättungsfaktor für kontinuierliches Kext-Lernen (Alpha). Standardwert 0,04 (Anpassungszeit von ca. 3–5 Tagen)."
|
||||
}
|
||||
},
|
||||
"heating_failure_detection": {
|
||||
@@ -776,7 +784,7 @@
|
||||
"options": {
|
||||
"thermostat_central_config": "Zentrale Konfiguration",
|
||||
"thermostat_over_switch": "Thermostat an einem Schalter",
|
||||
"thermostat_over_climate": "Thermostat an einem Thermostat",
|
||||
"thermostat_over_climate": "Thermostat mittels Thermostat/Klimaanlage",
|
||||
"thermostat_over_valve": "Thermostat an einem Ventil"
|
||||
}
|
||||
},
|
||||
@@ -1013,6 +1021,24 @@
|
||||
"description": "Der optionale Sperrcode"
|
||||
}
|
||||
}
|
||||
},
|
||||
"download_logs": {
|
||||
"name": "Download logs",
|
||||
"description": "Gefilterte Protokolle für eine VTherm-Entität erfassen und herunterladen.",
|
||||
"fields": {
|
||||
"log_level": {
|
||||
"name": "Log level",
|
||||
"description": "Mindestprotokollstufe für die Erfassung"
|
||||
},
|
||||
"period_start": {
|
||||
"name": "Period start",
|
||||
"description": "Erfassungszeitraum Start"
|
||||
},
|
||||
"period_end": {
|
||||
"name": "Period end",
|
||||
"description": "Erfassungszeitraum Ende"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
|
||||
@@ -933,6 +933,16 @@
|
||||
"boost_ac_away_temp": {
|
||||
"name": "Boost ac away"
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"auto_start_stop_stop_mode": {
|
||||
"name": "Auto start/stop stop mode",
|
||||
"state": {
|
||||
"off": "Off",
|
||||
"fan_only": "Fan only",
|
||||
"dry": "Dry"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
|
||||
@@ -935,6 +935,16 @@
|
||||
"boost_ac_away_temp": {
|
||||
"name": "Boost clim abs"
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"auto_start_stop_stop_mode": {
|
||||
"name": "Mode d'arrêt auto start/stop",
|
||||
"state": {
|
||||
"off": "Arrêt",
|
||||
"fan_only": "Ventilation seule",
|
||||
"dry": "Déshumidification"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
|
||||
@@ -860,8 +860,15 @@ class UnderlyingClimate(UnderlyingEntity):
|
||||
|
||||
_LOGGER.info("%s - Set setpoint temperature to: %s", self, target_temp)
|
||||
|
||||
# Issue 807 add TARGET_TEMPERATURE only if in the features
|
||||
if ClimateEntityFeature.TARGET_TEMPERATURE_RANGE in self.supported_features:
|
||||
# Issue 807 add TARGET_TEMPERATURE only if in the features.
|
||||
# Use a bitwise test (not the `in` operator): self.supported_features is read
|
||||
# from the underlying entity's state attributes, where Home Assistant may store
|
||||
# it as a plain int (e.g. for restored states). `EnumMember in <int>` raises
|
||||
# `TypeError: argument of type 'int' is not a container or iterable`, whereas
|
||||
# the bitwise `&` works for both a plain int and a ClimateEntityFeature IntFlag.
|
||||
# This matches the bitwise pattern already used elsewhere in this class
|
||||
# (fan_modes / swing_modes).
|
||||
if self.supported_features & ClimateEntityFeature.TARGET_TEMPERATURE_RANGE:
|
||||
data.update(
|
||||
{
|
||||
"target_temp_high": target_temp,
|
||||
@@ -869,7 +876,7 @@ class UnderlyingClimate(UnderlyingEntity):
|
||||
}
|
||||
)
|
||||
|
||||
if ClimateEntityFeature.TARGET_TEMPERATURE in self.supported_features:
|
||||
if self.supported_features & ClimateEntityFeature.TARGET_TEMPERATURE:
|
||||
data["temperature"] = target_temp
|
||||
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user