Feature 223 use fan control in over climate (#260)

* Issue #223 - add auto_fan_mode

* Update README

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
This commit is contained in:
Jean-Marc Collin
2023-12-09 18:39:54 +01:00
committed by GitHub
parent f7c4e20de3
commit 7851df84ec
20 changed files with 919 additions and 208 deletions

View File

@@ -102,7 +102,6 @@ from .const import (
CONF_TEMP_MIN,
HIDDEN_PRESETS,
CONF_AC_MODE,
UnknownEntity,
EventType,
ATTR_MEAN_POWER_CYCLE,
ATTR_TOTAL_ENERGY,
@@ -259,6 +258,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
self._ema_temp = None
self._ema_algo = None
self._now = None
self._attr_fan_mode = None
self.post_init(entry_infos)
def post_init(self, entry_infos):
@@ -555,11 +556,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
self.async_on_remove(self.remove_thermostat)
try:
await self.async_startup()
except UnknownEntity:
# Ingore this error which is possible if underlying climate is not found temporary
pass
await self.async_startup()
def remove_thermostat(self):
"""Called when the thermostat will be removed"""
@@ -577,12 +574,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
need_write_state = False
# Initialize all UnderlyingEntities
for under in self._underlyings:
try:
under.startup()
except UnknownEntity:
# Not found, we will try later
pass
self.init_underlyings()
temperature_state = self.hass.states.get(self._temp_sensor_entity_id)
if temperature_state and temperature_state.state not in (
@@ -723,6 +715,9 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
EVENT_HOMEASSISTANT_START, _async_startup_internal
)
def init_underlyings(self):
"""Initialize all underlyings. Should be overriden if necessary"""
def restore_specific_previous_state(self, old_state):
"""Should be overriden in each specific thermostat
if a specific previous state or attribute should be
@@ -2089,6 +2084,13 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
return shouldBeInSecurity
@property
def is_initialized(self) -> bool:
"""Check if all underlyings are initialized
This is usefull only for over_climate in which we
should have found the underlying climate to be operational"""
return True
async def async_control_heating(self, force=False, _=None):
"""The main function used to run the calculation at each cycle"""
@@ -2104,18 +2106,10 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
await self._async_manage_window_auto(in_cycle=True)
# Issue 56 in over_climate mode, if the underlying climate is not initialized, try to initialize it
for under in self._underlyings:
if not under.is_initialized:
_LOGGER.info(
"%s - Underlying %s is not initialized. Try to initialize it",
self,
under.entity_id,
)
try:
under.startup()
except UnknownEntity:
# still not found, we an stop here
return False
if not self.is_initialized:
if not self.init_underlyings():
# still not found, we an stop here
return False
# Check overpowering condition
# Not necessary for switch because each switch is checking at startup

View File

@@ -15,7 +15,13 @@ from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers import entity_platform
from homeassistant.const import CONF_NAME, STATE_ON, STATE_OFF, STATE_HOME, STATE_NOT_HOME
from homeassistant.const import (
CONF_NAME,
STATE_ON,
STATE_OFF,
STATE_HOME,
STATE_NOT_HOME,
)
from .const import (
DOMAIN,
@@ -26,10 +32,11 @@ from .const import (
SERVICE_SET_SECURITY,
SERVICE_SET_WINDOW_BYPASS,
SERVICE_SET_AUTO_REGULATION_MODE,
SERVICE_SET_AUTO_FAN_MODE,
CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_CLIMATE,
CONF_THERMOSTAT_VALVE
CONF_THERMOSTAT_VALVE,
)
from .thermostat_switch import ThermostatOverSwitch
@@ -102,8 +109,7 @@ async def async_setup_entry(
platform.async_register_entity_service(
SERVICE_SET_WINDOW_BYPASS,
{
vol.Required("window_bypass"): vol.In([True, False]
),
vol.Required("window_bypass"): vol.In([True, False]),
},
"service_set_window_bypass_state",
)
@@ -111,7 +117,19 @@ async def async_setup_entry(
platform.async_register_entity_service(
SERVICE_SET_AUTO_REGULATION_MODE,
{
vol.Required("auto_regulation_mode"): vol.In(["None", "Light", "Medium", "Strong", "Slow"]),
vol.Required("auto_regulation_mode"): vol.In(
["None", "Light", "Medium", "Strong", "Slow"]
),
},
"service_set_auto_regulation_mode",
)
platform.async_register_entity_service(
SERVICE_SET_AUTO_FAN_MODE,
{
vol.Required("auto_fan_mode"): vol.In(
["None", "Low", "Medium", "High", "Turbo"]
),
},
"service_set_auto_fan_mode",
)

View File

@@ -107,6 +107,9 @@ from .const import (
CONF_INVERSE_SWITCH,
UnknownEntity,
WindowOpenDetectionMethod,
CONF_AUTO_FAN_MODES,
CONF_AUTO_FAN_MODE,
CONF_AUTO_FAN_HIGH,
)
_LOGGER = logging.getLogger(__name__)
@@ -275,6 +278,14 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
vol.Optional(
CONF_AUTO_REGULATION_PERIOD_MIN, default=5
): cv.positive_int,
vol.Optional(
CONF_AUTO_FAN_MODE, default=CONF_AUTO_FAN_HIGH
): selector.SelectSelector(
selector.SelectSelectorConfig(
options=CONF_AUTO_FAN_MODES,
translation_key="auto_fan_mode",
)
),
}
)

View File

@@ -95,6 +95,12 @@ CONF_AUTO_REGULATION_DTEMP = "auto_regulation_dtemp"
CONF_AUTO_REGULATION_PERIOD_MIN = "auto_regulation_periode_min"
CONF_INVERSE_SWITCH = "inverse_switch_command"
CONF_SHORT_EMA_PARAMS = "short_ema_params"
CONF_AUTO_FAN_MODE = "auto_fan_mode"
CONF_AUTO_FAN_NONE = "auto_fan_none"
CONF_AUTO_FAN_LOW = "auto_fan_low"
CONF_AUTO_FAN_MEDIUM = "auto_fan_medium"
CONF_AUTO_FAN_HIGH = "auto_fan_high"
CONF_AUTO_FAN_TURBO = "auto_fan_turbo"
DEFAULT_SHORT_EMA_PARAMS = {
"max_alpha": 0.5,
@@ -233,6 +239,14 @@ CONF_THERMOSTAT_TYPES = [
CONF_THERMOSTAT_VALVE,
]
CONF_AUTO_FAN_MODES = [
CONF_AUTO_FAN_NONE,
CONF_AUTO_FAN_LOW,
CONF_AUTO_FAN_MEDIUM,
CONF_AUTO_FAN_HIGH,
CONF_AUTO_FAN_TURBO,
]
SUPPORT_FLAGS = ClimateEntityFeature.TARGET_TEMPERATURE
SERVICE_SET_PRESENCE = "set_presence"
@@ -240,6 +254,7 @@ SERVICE_SET_PRESET_TEMPERATURE = "set_preset_temperature"
SERVICE_SET_SECURITY = "set_security"
SERVICE_SET_WINDOW_BYPASS = "set_window_bypass"
SERVICE_SET_AUTO_REGULATION_MODE = "set_auto_regulation_mode"
SERVICE_SET_AUTO_FAN_MODE = "set_auto_fan_mode"
DEFAULT_SECURITY_MIN_ON_PERCENT = 0.5
DEFAULT_SECURITY_DEFAULT_ON_PERCENT = 0.1
@@ -247,6 +262,9 @@ DEFAULT_SECURITY_DEFAULT_ON_PERCENT = 0.1
ATTR_TOTAL_ENERGY = "total_energy"
ATTR_MEAN_POWER_CYCLE = "mean_cycle_power"
AUTO_FAN_DTEMP_THRESHOLD = 2
AUTO_FAN_DEACTIVATED_MODES = ["mute", "auto", "low"]
# A special regulation parameter suggested by @Maia here: https://github.com/jmcollin78/versatile_thermostat/discussions/154
class RegulationParamSlow:

View File

@@ -161,3 +161,25 @@ set_auto_regulation_mode:
- "Strong"
- "Slow"
- "Expert"
set_auto_fan_mode:
name: Set Auto Fan mode
description: Change the mode of auto-fan (only for VTherm over climate)
target:
entity:
integration: versatile_thermostat
fields:
auto_fan_mode:
name: Auto fan mode
description: Possible values
required: true
advanced: false
default: true
selector:
select:
options:
- "None"
- "Low"
- "Medium"
- "High"
- "Turbo"

View File

@@ -25,24 +25,25 @@
"title": "Linked entities",
"description": "Linked entities attributes",
"data": {
"heater_entity_id": "1rst heater switch",
"heater_entity_id": "1st heater switch",
"heater_entity2_id": "2nd heater switch",
"heater_entity3_id": "3rd heater switch",
"heater_entity4_id": "4th heater switch",
"proportional_function": "Algorithm",
"climate_entity_id": "1rst underlying climate",
"climate_entity_id": "1st underlying climate",
"climate_entity2_id": "2nd underlying climate",
"climate_entity3_id": "3rd underlying climate",
"climate_entity4_id": "4th underlying climate",
"ac_mode": "AC mode",
"valve_entity_id": "1rst valve number",
"valve_entity_id": "1st valve number",
"valve_entity2_id": "2nd valve number",
"valve_entity3_id": "3rd valve number",
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period",
"inverse_switch_command": "Inverse switch command"
"inverse_switch_command": "Inverse switch command",
"auto_fan_mode": " Auto fan mode"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -55,14 +56,15 @@
"climate_entity3_id": "3rd underlying climate entity id",
"climate_entity4_id": "4th underlying climate entity id",
"ac_mode": "Use the Air Conditioning (AC) mode",
"valve_entity_id": "1rst valve number entity id",
"valve_entity_id": "1st valve number entity id",
"valve_entity2_id": "2nd valve number entity id",
"valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be sent",
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command"
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary"
}
},
"tpi": {
@@ -75,7 +77,7 @@
},
"presets": {
"title": "Presets",
"description": "For each presets, give the target temperature (0 to ignore preset)",
"description": "For each preset set the target temperature (0 to ignore preset)",
"data": {
"eco_temp": "Temperature in Eco preset",
"comfort_temp": "Temperature in Comfort preset",
@@ -96,16 +98,16 @@
"window_auto_max_duration": "Maximum duration of automatic window open detection (in min)"
},
"data_description": {
"window_sensor_entity_id": "Leave empty if no window sensor should be use",
"window_sensor_entity_id": "Leave empty if no window sensor should be used",
"window_delay": "The delay in seconds before sensor detection is taken into account",
"window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not use",
"window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not use",
"window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not use"
"window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used",
"window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not used",
"window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used"
}
},
"motion": {
"title": "Motion management",
"description": "Motion sensor management. Preset can switch automatically depending of a motion detection\nLeave corresponding entity_id empty if not used.\nmotion_preset and no_motion_preset should be set to the corresponding preset name",
"description": "Motion sensor management. Preset can switch automatically depending on motion detection\nLeave corresponding entity_id empty if not used.\nmotion_preset and no_motion_preset should be set to the corresponding preset name",
"data": {
"motion_sensor_entity_id": "Motion sensor entity id",
"motion_delay": "Activation delay",
@@ -115,7 +117,7 @@
},
"data_description": {
"motion_sensor_entity_id": "The entity id of the motion sensor",
"motion_delay": "Motion activation activation delay (seconds)",
"motion_delay": "Motion activation delay (seconds)",
"motion_off_delay": "Motion deactivation delay (seconds)",
"motion_preset": "Preset to use when motion is detected",
"no_motion_preset": "Preset to use when no motion is detected"
@@ -145,7 +147,7 @@
},
"advanced": {
"title": "Advanced parameters",
"description": "Configuration of advanced parameters. Leave the default values if you don't know what you are doing.\nThis parameters can lead to a very bad temperature or power regulation.",
"description": "Configuration of advanced parameters. Leave the default values if you don't know what you are doing.\nThese parameters can lead to very poor temperature control or bad power regulation.",
"data": {
"minimal_activation_delay": "Minimal activation delay",
"security_delay_min": "Security delay (in minutes)",
@@ -154,16 +156,16 @@
},
"data_description": {
"minimal_activation_delay": "Delay in seconds under which the equipment will not be activated",
"security_delay_min": "Maximum allowed delay in minutes between two temperature mesures. Above this delay, the thermostat will turn to a security off state",
"security_delay_min": "Maximum allowed delay in minutes between two temperature measurements. Above this delay the thermostat will turn to a security off state",
"security_min_on_percent": "Minimal heating percent value for security preset activation. Below this amount of power percent the thermostat won't go into security preset",
"security_default_on_percent": "The default heating power percent value in security preset. Set to 0 to switch off heater in security present"
"security_default_on_percent": "The default heating power percent value in security preset. Set to 0 to switch off heater in security preset"
}
}
},
"error": {
"unknown": "Unexpected error",
"unknown_entity": "Unknown entity id",
"window_open_detection_method": "Only one window open detection method should be used. Use sensor or automatic detection through temperature threshold but not both"
"window_open_detection_method": "Only one window open detection method should be used. Use either window sensor or automatic detection through temperature threshold but not both"
},
"abort": {
"already_configured": "Device is already configured"
@@ -194,24 +196,25 @@
"title": "Linked entities",
"description": "Linked entities attributes",
"data": {
"heater_entity_id": "1rst heater switch",
"heater_entity_id": "1st heater switch",
"heater_entity2_id": "2nd heater switch",
"heater_entity3_id": "3rd heater switch",
"heater_entity4_id": "4th heater switch",
"proportional_function": "Algorithm",
"climate_entity_id": "1rst underlying climate",
"climate_entity_id": "1st underlying climate",
"climate_entity2_id": "2nd underlying climate",
"climate_entity3_id": "3rd underlying climate",
"climate_entity4_id": "4th underlying climate",
"ac_mode": "AC mode",
"valve_entity_id": "1rst valve number",
"valve_entity_id": "1st valve number",
"valve_entity2_id": "2nd valve number",
"valve_entity3_id": "3rd valve number",
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period",
"inverse_switch_command": "Inverse switch command"
"inverse_switch_command": "Inverse switch command",
"auto_fan_mode": " Auto fan mode"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -224,14 +227,15 @@
"climate_entity3_id": "3rd underlying climate entity id",
"climate_entity4_id": "4th underlying climate entity id",
"ac_mode": "Use the Air Conditioning (AC) mode",
"valve_entity_id": "1rst valve number entity id",
"valve_entity_id": "1st valve number entity id",
"valve_entity2_id": "2nd valve number entity id",
"valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be sent",
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command"
"inverse_switch_command": "For switch with pilot wire and diode you may need to invert the command",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary"
}
},
"tpi": {
@@ -244,7 +248,7 @@
},
"presets": {
"title": "Presets",
"description": "For each presets, give the target temperature (0 to ignore preset)",
"description": "For each preset set the target temperature (0 to ignore preset)",
"data": {
"eco_temp": "Temperature in Eco preset",
"comfort_temp": "Temperature in Comfort preset",
@@ -265,11 +269,11 @@
"window_auto_max_duration": "Maximum duration of automatic window open detection (in min)"
},
"data_description": {
"window_sensor_entity_id": "Leave empty if no window sensor should be use",
"window_sensor_entity_id": "Leave empty if no window sensor should be used",
"window_delay": "The delay in seconds before sensor detection is taken into account",
"window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not use",
"window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not use",
"window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not use"
"window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used",
"window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not used",
"window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used"
}
},
"motion": {
@@ -284,7 +288,7 @@
},
"data_description": {
"motion_sensor_entity_id": "The entity id of the motion sensor",
"motion_delay": "Motion activation activation delay (seconds)",
"motion_delay": "Motion activation delay (seconds)",
"motion_off_delay": "Motion deactivation delay (seconds)",
"motion_preset": "Preset to use when motion is detected",
"no_motion_preset": "Preset to use when no motion is detected"
@@ -303,7 +307,7 @@
"title": "Presence management",
"description": "Presence management attributes.\nGives the a presence sensor of your home (true is someone is present).\nThen specify either the preset to use when presence sensor is false or the offset in temperature to apply.\nIf preset is given, the offset will not be used.\nLeave corresponding entity_id empty if not used.",
"data": {
"presence_sensor_entity_id": "Presence sensor entity id (true is present)",
"presence_sensor_entity_id": "Presence sensor entity id",
"eco_away_temp": "Temperature in Eco preset when no presence",
"comfort_away_temp": "Temperature in Comfort preset when no presence",
"boost_away_temp": "Temperature in Boost preset when no presence",
@@ -314,25 +318,25 @@
},
"advanced": {
"title": "Advanced parameters",
"description": "Configuration of advanced parameters. Leave the default values if you don't know what you are doing.\nThis parameters can lead to a very bad temperature or power regulation.",
"description": "Configuration of advanced parameters. Leave the default values if you don't know what you are doing.\nThese parameters can lead to very poor temperature control or bad power regulation.",
"data": {
"minimal_activation_delay": "Minimal activation delay",
"security_delay_min": "Security delay (in minutes)",
"security_min_on_percent": "Minimal power percent for security mode",
"security_min_on_percent": "Minimal power percent to enable security mode",
"security_default_on_percent": "Power percent to use in security mode"
},
"data_description": {
"minimal_activation_delay": "Delay in seconds under which the equipment will not be activated",
"security_delay_min": "Maximum allowed delay in minutes between two temperature mesures. Above this delay, the thermostat will turn to a security off state",
"security_delay_min": "Maximum allowed delay in minutes between two temperature measurements. Above this delay the thermostat will turn to a security off state",
"security_min_on_percent": "Minimal heating percent value for security preset activation. Below this amount of power percent the thermostat won't go into security preset",
"security_default_on_percent": "The default heating power percent value in security preset. Set to 0 to switch off heater in security present"
"security_default_on_percent": "The default heating power percent value in security preset. Set to 0 to switch off heater in security preset"
}
}
},
"error": {
"unknown": "Unexpected error",
"unknown_entity": "Unknown entity id",
"window_open_detection_method": "Only one window open detection method should be used. Use sensor or automatic detection through temperature threshold but not both"
"window_open_detection_method": "Only one window open detection method should be used. Use either window sensor or automatic detection through temperature threshold but not both"
},
"abort": {
"already_configured": "Device is already configured"
@@ -355,6 +359,15 @@
"auto_regulation_expert": "Expert",
"auto_regulation_none": "No auto-regulation"
}
},
"auto_fan_mode": {
"options": {
"auto_fan_none": "No auto fan",
"auto_fan_low": "Low",
"auto_fan_medium": "Medium",
"auto_fan_high": "High",
"auto_fan_turbo": "Turbo"
}
}
},
"entity": {

View File

@@ -9,7 +9,7 @@ from homeassistant.helpers.event import (
async_track_time_interval,
)
from homeassistant.components.climate import HVACAction, HVACMode
from homeassistant.components.climate import HVACAction, HVACMode, ClimateEntityFeature
from .commons import NowClass, round_to_nearest
from .base_thermostat import BaseThermostat
@@ -31,10 +31,19 @@ from .const import (
CONF_AUTO_REGULATION_EXPERT,
CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN,
CONF_AUTO_FAN_MODE,
CONF_AUTO_FAN_NONE,
CONF_AUTO_FAN_LOW,
CONF_AUTO_FAN_MEDIUM,
CONF_AUTO_FAN_HIGH,
CONF_AUTO_FAN_TURBO,
RegulationParamSlow,
RegulationParamLight,
RegulationParamMedium,
RegulationParamStrong,
AUTO_FAN_DTEMP_THRESHOLD,
AUTO_FAN_DEACTIVATED_MODES,
UnknownEntity,
)
from .vtherm_api import VersatileThermostatAPI
@@ -52,6 +61,9 @@ class ThermostatOverClimate(BaseThermostat):
_auto_regulation_dtemp: float = None
_auto_regulation_period_min: int = None
_last_regulation_change: datetime = None
_auto_fan_mode: str = None
_auto_activated_fan_mode: str = None
_auto_deactivated_fan_mode: str = None
_entity_component_unrecorded_attributes = (
BaseThermostat._entity_component_unrecorded_attributes.union(
@@ -65,6 +77,9 @@ class ThermostatOverClimate(BaseThermostat):
"underlying_climate_3",
"regulation_accumulated_error",
"auto_regulation_mode",
"auto_fan_mode",
"auto_activated_fan_mode",
"auto_deactivated_fan_mode",
}
)
)
@@ -164,6 +179,41 @@ class ThermostatOverClimate(BaseThermostat):
self.regulated_target_temp, self._attr_max_temp, self._attr_min_temp
)
async def _send_auto_fan_mode(self):
"""Send the fan mode if auto_fan_mode and temperature gap is > threshold"""
if not self._auto_fan_mode or not self._auto_activated_fan_mode:
return
dtemp = (
self.regulated_target_temp if self.is_regulated else self.target_temperature
)
if dtemp is None or self.current_temperature is None:
return
dtemp = dtemp - self.current_temperature
should_activate_auto_fan = (
dtemp >= AUTO_FAN_DTEMP_THRESHOLD or dtemp <= -AUTO_FAN_DTEMP_THRESHOLD
)
if should_activate_auto_fan and self.fan_mode != self._auto_activated_fan_mode:
_LOGGER.info(
"%s - Activate the auto fan mode with %s because delta temp is %.2f",
self,
self._auto_fan_mode,
dtemp,
)
await self.async_set_fan_mode(self._auto_activated_fan_mode)
if (
not should_activate_auto_fan
and self.fan_mode not in AUTO_FAN_DEACTIVATED_MODES
):
_LOGGER.info(
"%s - DeActivate the auto fan mode with %s because delta temp is %.2f",
self,
self._auto_deactivated_fan_mode,
dtemp,
)
await self.async_set_fan_mode(self._auto_deactivated_fan_mode)
@overrides
def post_init(self, entry_infos):
"""Initialize the Thermostat"""
@@ -201,6 +251,12 @@ class ThermostatOverClimate(BaseThermostat):
else 5
)
self._auto_fan_mode = (
entry_infos.get(CONF_AUTO_FAN_MODE)
if entry_infos.get(CONF_AUTO_FAN_MODE) is not None
else CONF_AUTO_FAN_NONE
)
def choose_auto_regulation_mode(self, auto_regulation_mode):
"""Choose or change the regulation mode"""
self._auto_regulation_mode = auto_regulation_mode
@@ -277,6 +333,47 @@ class ThermostatOverClimate(BaseThermostat):
self.target_temperature, 0, 0, 0, 0, 0.1, 0
)
def choose_auto_fan_mode(self, auto_fan_mode):
"""Choose the correct fan mode depending of the underlying capacities and the configuration"""
# Get the supported feature of the first underlying. We suppose each underlying have the same fan attributes
fan_supported = self.supported_features & ClimateEntityFeature.FAN_MODE > 0
if auto_fan_mode == CONF_AUTO_FAN_NONE or not fan_supported:
self._auto_activated_fan_mode = self._auto_deactivated_fan_mode = None
return
def find_fan_mode(fan_modes, fan_mode) -> str:
"""Return the fan_mode if it exist of None if not"""
try:
return fan_mode if fan_modes.index(fan_mode) >= 0 else None
except ValueError:
return None
fan_modes = self.fan_modes
if auto_fan_mode == CONF_AUTO_FAN_LOW:
self._auto_activated_fan_mode = find_fan_mode(fan_modes, "low")
elif auto_fan_mode == CONF_AUTO_FAN_MEDIUM:
self._auto_activated_fan_mode = find_fan_mode(fan_modes, "mid")
elif auto_fan_mode == CONF_AUTO_FAN_HIGH:
self._auto_activated_fan_mode = find_fan_mode(fan_modes, "high")
elif auto_fan_mode == CONF_AUTO_FAN_TURBO:
self._auto_activated_fan_mode = find_fan_mode(
fan_modes, "turbo"
) or find_fan_mode(fan_modes, "high")
for val in AUTO_FAN_DEACTIVATED_MODES:
if find_fan_mode(fan_modes, val):
self._auto_deactivated_fan_mode = val
break
_LOGGER.info(
"%s - choose_auto_fan_mode founds auto_activated_fan_mode=%s and auto_deactivated_fan_mode=%s",
self,
self._auto_activated_fan_mode,
self._auto_deactivated_fan_mode,
)
@overrides
async def async_added_to_hass(self):
"""Run when entity about to be added."""
@@ -302,6 +399,9 @@ class ThermostatOverClimate(BaseThermostat):
)
)
# init auto_regulation_mode
self.choose_auto_regulation_mode(self._auto_regulation_mode)
@overrides
def restore_specific_previous_state(self, old_state):
"""Restore my specific attributes from previous state"""
@@ -348,6 +448,14 @@ class ThermostatOverClimate(BaseThermostat):
"regulation_accumulated_error"
] = self._regulation_algo.accumulated_error
self._attr_extra_state_attributes["auto_fan_mode"] = self.auto_fan_mode
self._attr_extra_state_attributes[
"auto_activated_fan_mode"
] = self._auto_activated_fan_mode
self._attr_extra_state_attributes[
"auto_deactivated_fan_mode"
] = self._auto_deactivated_fan_mode
self.async_write_ha_state()
_LOGGER.debug(
"%s - Calling update_custom_attributes: %s",
@@ -435,6 +543,12 @@ class ThermostatOverClimate(BaseThermostat):
else None
)
new_fan_mode = (
new_state.attributes.get("fan_mode")
if new_state and new_state.attributes
else None
)
old_state_date_changed = (
old_state.last_changed if old_state and old_state.last_changed else None
)
@@ -545,6 +659,11 @@ class ThermostatOverClimate(BaseThermostat):
for under in self._underlyings:
await under.set_hvac_mode(new_hvac_mode)
# A quick win to known if it has change by using the self._attr_fan_mode and not only underlying[0].fan_mode
if new_fan_mode != self._attr_fan_mode:
self._attr_fan_mode = new_fan_mode
changes = True
if not changes:
# try to manage new target temperature set if state
_LOGGER.debug(
@@ -576,6 +695,9 @@ class ThermostatOverClimate(BaseThermostat):
await self._send_regulated_temperature()
if self._auto_fan_mode and self._auto_fan_mode != CONF_AUTO_FAN_NONE:
await self._send_auto_fan_mode()
return ret
@property
@@ -583,6 +705,11 @@ class ThermostatOverClimate(BaseThermostat):
"""Get the regulation mode"""
return self._auto_regulation_mode
@property
def auto_fan_mode(self):
"""Get the auto fan mode"""
return self._auto_fan_mode
@property
def regulated_target_temp(self):
"""Get the regulated target temperature"""
@@ -613,7 +740,8 @@ class ThermostatOverClimate(BaseThermostat):
Requires ClimateEntityFeature.FAN_MODE.
"""
if self.underlying_entity(0):
return self.underlying_entity(0).fan_mode
self._attr_fan_mode = self.underlying_entity(0).fan_mode
return self._attr_fan_mode
return None
@@ -707,6 +835,31 @@ class ThermostatOverClimate(BaseThermostat):
return None
@property
def is_initialized(self) -> bool:
"""Check if all underlyings are initialized"""
for under in self._underlyings:
if not under.is_initialized:
return False
return True
@overrides
def init_underlyings(self):
"""Init the underlyings if not already done"""
for under in self._underlyings:
if not under.is_initialized:
_LOGGER.info(
"%s - Underlying %s is not initialized. Try to initialize it",
self,
under.entity_id,
)
try:
under.startup()
except UnknownEntity:
# still not found, we an stop here
return False
self.choose_auto_fan_mode(self._auto_fan_mode)
@overrides
def turn_aux_heat_on(self) -> None:
"""Turn auxiliary heater on."""
@@ -795,3 +948,30 @@ class ThermostatOverClimate(BaseThermostat):
await self._send_regulated_temperature()
self.update_custom_attributes()
async def service_set_auto_fan_mode(self, auto_fan_mode):
"""Called by a service call:
service: versatile_thermostat.set_auto_fan_mode
data:
auto_fan_mode: [None | Low | Medium | High | Turbo]
target:
entity_id: climate.thermostat_1
"""
_LOGGER.info(
"%s - Calling service_set_auto_fan_mode, auto_fan_mode: %s",
self,
auto_fan_mode,
)
if auto_fan_mode == "None":
self.choose_auto_fan_mode(CONF_AUTO_FAN_NONE)
elif auto_fan_mode == "Low":
self.choose_auto_fan_mode(CONF_AUTO_FAN_LOW)
elif auto_fan_mode == "Medium":
self.choose_auto_fan_mode(CONF_AUTO_FAN_MEDIUM)
elif auto_fan_mode == "High":
self.choose_auto_fan_mode(CONF_AUTO_FAN_HIGH)
elif auto_fan_mode == "Turbo":
self.choose_auto_fan_mode(CONF_AUTO_FAN_TURBO)
await self._send_regulated_temperature()
self.update_custom_attributes()

View File

@@ -42,7 +42,8 @@
"auto_regulation_mode": "Αυτόματη ρύθμιση",
"auto_regulation_dtemp": "Όριο ρύθμισης",
"auto_regulation_periode_min": "Ελάχιστη περίοδος ρύθμισης",
"inverse_switch_command": "Αντίστροφη εντολή διακόπτη"
"inverse_switch_command": "Αντίστροφη εντολή διακόπτη",
"auto_fan_mode": " Auto fan mode"
},
"data_description": {
"heater_entity_id": "Υποχρεωτική ταυτότητα οντότητας θερμαντήρα",
@@ -62,7 +63,8 @@
"auto_regulation_mode": "Αυτόματη προσαρμογή της στοχευμένης θερμοκρασίας",
"auto_regulation_dtemp": "Το όριο σε ° κάτω από το οποίο η αλλαγή θερμοκρασίας δεν θα αποστέλλεται",
"auto_regulation_periode_min": "Διάρκεια σε λεπτά μεταξύ δύο ενημερώσεων ρύθμισης",
"inverse_switch_command": "Για διακόπτη με πιλοτικό καλώδιο και δίοδο μπορεί να χρειαστεί να αντιστρέψετε την εντολή"
"inverse_switch_command": "Για διακόπτη με πιλοτικό καλώδιο και δίοδο μπορεί να χρειαστεί να αντιστρέψετε την εντολή",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary"
}
},
"tpi": {
@@ -211,7 +213,8 @@
"auto_regulation_mode": "Αυτορύθμιση",
"auto_regulation_dtemp": "Όριο ρύθμισης",
"auto_regulation_periode_min": "Ελάχιστη περίοδος ρύθμισης",
"inverse_switch_command": "Αντίστροφη εντολή διακόπτη"
"inverse_switch_command": "Αντίστροφη εντολή διακόπτη",
"auto_fan_mode": " Auto fan mode"
},
"data_description": {
"heater_entity_id": "Υποχρεωτική ταυτότητα οντότητας θερμαντήρα",
@@ -231,7 +234,8 @@
"auto_regulation_mode": "Αυτόματη ρύθμιση της στοχευόμενης θερμοκρασίας",
"auto_regulation_dtemp": "Το κατώφλι σε °C κάτω από το οποίο η αλλαγή της θερμοκρασίας δεν θα αποστέλλεται",
"auto_regulation_periode_min": "Διάρκεια σε λεπτά μεταξύ δύο ενημερώσεων ρύθμισης",
"inverse_switch_command": "Για διακόπτες με πιλοτικό καλώδιο και δίοδο μπορεί να χρειαστεί να αντιστραφεί η εντολή"
"inverse_switch_command": "Για διακόπτες με πιλοτικό καλώδιο και δίοδο μπορεί να χρειαστεί να αντιστραφεί η εντολή",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary"
}
},
"tpi": {
@@ -355,6 +359,15 @@
"auto_regulation_expert": "Εμπειρογνώμων",
"auto_regulation_none": "Χωρίς αυτόματη ρύθμιση"
}
},
"auto_fan_mode": {
"options": {
"auto_fan_none": "No auto fan",
"auto_fan_low": "Low",
"auto_fan_medium": "Medium",
"auto_fan_high": "High",
"auto_fan_turbo": "Turbo"
}
}
},
"entity": {

View File

@@ -42,7 +42,8 @@
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period",
"inverse_switch_command": "Inverse switch command"
"inverse_switch_command": "Inverse switch command",
"auto_fan_mode": " Auto fan mode"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -62,7 +63,8 @@
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be sent",
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"inverse_switch_command": "For switch with pilot wire and diode you may need to invert the command"
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary"
}
},
"tpi": {
@@ -211,7 +213,8 @@
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period",
"inverse_switch_command": "Inverse switch command"
"inverse_switch_command": "Inverse switch command",
"auto_fan_mode": " Auto fan mode"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -231,7 +234,8 @@
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be sent",
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"inverse_switch_command": "For switch with pilot wire and diode you may need to invert the command"
"inverse_switch_command": "For switch with pilot wire and diode you may need to invert the command",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary"
}
},
"tpi": {
@@ -355,6 +359,15 @@
"auto_regulation_expert": "Expert",
"auto_regulation_none": "No auto-regulation"
}
},
"auto_fan_mode": {
"options": {
"auto_fan_none": "No auto fan",
"auto_fan_low": "Low",
"auto_fan_medium": "Medium",
"auto_fan_high": "High",
"auto_fan_turbo": "Turbo"
}
}
},
"entity": {

View File

@@ -42,7 +42,8 @@
"auto_regulation_mode": "Auto-régulation",
"auto_regulation_dtemp": "Seuil de régulation",
"auto_regulation_periode_min": "Période minimale de régulation",
"inverse_switch_command": "Inverser la commande"
"inverse_switch_command": "Inverser la commande",
"auto_fan_mode": " Auto ventilation mode"
},
"data_description": {
"heater_entity_id": "Entity id du 1er radiateur obligatoire",
@@ -62,7 +63,8 @@
"auto_regulation_mode": "Ajustement automatique de la température cible",
"auto_regulation_dtemp": "Le seuil en ° au-dessous duquel la régulation ne sera pas envoyée",
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode"
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode",
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important"
}
},
"tpi": {
@@ -212,7 +214,8 @@
"auto_regulation_mode": "Auto-regulation",
"auto_regulation_dtemp": "Seuil de régulation",
"auto_regulation_periode_min": "Période minimale de régulation",
"inverse_switch_command": "Inverser la commande"
"inverse_switch_command": "Inverser la commande",
"auto_fan_mode": " Auto fan mode"
},
"data_description": {
"heater_entity_id": "Entity id du 1er radiateur obligatoire",
@@ -232,7 +235,8 @@
"auto_regulation_mode": "Ajustement automatique de la consigne",
"auto_regulation_dtemp": "Le seuil en ° au-dessous duquel la régulation ne sera pas envoyée",
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode"
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode",
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important"
}
},
"tpi": {
@@ -356,6 +360,15 @@
"auto_regulation_expert": "Expert",
"auto_regulation_none": "Aucune"
}
},
"auto_fan_mode": {
"options": {
"auto_fan_none": "Pas d'auto fan",
"auto_fan_low": "Faible",
"auto_fan_medium": "Moyenne",
"auto_fan_high": "Forte",
"auto_fan_turbo": "Turbo"
}
}
},
"entity": {

View File

@@ -40,7 +40,8 @@
"valve_entity3_id": "Terza valvola",
"valve_entity4_id": "Quarta valvola",
"auto_regulation_mode": "Autoregolamentazione",
"inverse_switch_command": "Comando inverso"
"inverse_switch_command": "Comando inverso",
"auto_fan_mode": " Auto fan mode"
},
"data_description": {
"heater_entity_id": "Entity id obbligatoria del primo riscaldatore",
@@ -58,7 +59,8 @@
"valve_entity3_id": "Entity id della terza valvola",
"valve_entity4_id": "Entity id della quarta valvola",
"auto_regulation_mode": "Regolazione automatica della temperatura target",
"inverse_switch_command": "Inverte il controllo dell'interruttore per un'installazione con filo pilota e diodo"
"inverse_switch_command": "Inverte il controllo dell'interruttore per un'installazione con filo pilota e diodo",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary"
}
},
"tpi": {
@@ -198,7 +200,8 @@
"valve_entity3_id": "Terza valvola",
"valve_entity4_id": "Quarta valvola",
"auto_regulation_mode": "Autoregolamentazione",
"inverse_switch_command": "Comando inverso"
"inverse_switch_command": "Comando inverso",
"auto_fan_mode": " Auto fan mode"
},
"data_description": {
"heater_entity_id": "Entity id obbligatoria del primo riscaldatore",
@@ -216,7 +219,8 @@
"valve_entity3_id": "Entity id della terza valvola",
"valve_entity4_id": "Entity id della quarta valvola",
"auto_regulation_mode": "Autoregolamentazione",
"inverse_switch_command": "Inverte il controllo dell'interruttore per un'installazione con filo pilota e diodo"
"inverse_switch_command": "Inverte il controllo dell'interruttore per un'installazione con filo pilota e diodo",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary"
}
},
"tpi": {
@@ -333,6 +337,15 @@
"auto_regulation_expert": "Esperto",
"auto_regulation_none": "Nessuna autoregolamentazione"
}
},
"auto_fan_mode": {
"options": {
"auto_fan_none": "Nessune autofan",
"auto_fan_low": "Leggera",
"auto_fan_medium": "Media",
"auto_fan_high": "Forte",
"auto_fan_turbo": "Turbo"
}
}
},
"entity": {

View File

@@ -42,7 +42,8 @@
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period",
"inverse_switch_command": "Inverse switch command"
"inverse_switch_command": "Inverse switch command",
"auto_fan_mode": " Auto fan mode"
},
"data_description": {
"heater_entity_id": "ID entity povinného ohrievača",
@@ -62,7 +63,8 @@
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command"
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary"
}
},
"tpi": {
@@ -211,7 +213,8 @@
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period",
"inverse_switch_command": "Inverse switch command"
"inverse_switch_command": "Inverse switch command",
"auto_fan_mode": " Auto fan mode"
},
"data_description": {
"heater_entity_id": "ID entity povinného ohrievača",
@@ -231,7 +234,8 @@
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command"
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary"
}
},
"tpi": {
@@ -355,6 +359,15 @@
"auto_regulation_expert": "Expert",
"auto_regulation_none": "No auto-regulation"
}
},
"auto_fan_mode": {
"options": {
"auto_fan_none": "No auto-fan",
"auto_fan_low": "Low",
"auto_fan_medium": "Medium",
"auto_fan_high": "High",
"auto_fan_turbo": "Turbo"
}
}
},
"entity": {