diff --git a/custom_components/versatile_thermostat/base_thermostat.py b/custom_components/versatile_thermostat/base_thermostat.py index 00ba822..f8a7187 100644 --- a/custom_components/versatile_thermostat/base_thermostat.py +++ b/custom_components/versatile_thermostat/base_thermostat.py @@ -460,8 +460,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]): else DEFAULT_SECURITY_DEFAULT_ON_PERCENT ) self._minimal_activation_delay = entry_infos.get(CONF_MINIMAL_ACTIVATION_DELAY) - self._last_temperature_measure = datetime.now(tz=self._current_tz) - self._last_ext_temperature_measure = datetime.now(tz=self._current_tz) + self._last_temperature_measure = self.now + self._last_ext_temperature_measure = self.now self._security_state = False # Initiate the ProportionalAlgorithm @@ -1342,7 +1342,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]): self, old_preset_mode: str | None = None ): # pylint: disable=unused-argument """Reset to now the last change time""" - self._last_change_time = datetime.now(tz=self._current_tz) + self._last_change_time = self.now _LOGGER.debug("%s - last_change_time is now %s", self, self._last_change_time) def reset_last_temperature_time(self, old_preset_mode: str | None = None): @@ -1352,7 +1352,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]): and old_preset_mode not in HIDDEN_PRESETS ): self._last_temperature_measure = self._last_ext_temperature_measure = ( - datetime.now(tz=self._current_tz) + self.now ) def find_preset_temp(self, preset_mode: str): @@ -1458,16 +1458,16 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]): """Extract the last_changed state from State or return now if not available""" return ( state.last_changed.astimezone(self._current_tz) - if state.last_changed is not None - else datetime.now(tz=self._current_tz) + if isinstance(state.last_changed, datetime) + else self.now ) def get_last_updated_date_or_now(self, state: State) -> datetime: """Extract the last_changed state from State or return now if not available""" return ( state.last_updated.astimezone(self._current_tz) - if state.last_updated is not None - else datetime.now(tz=self._current_tz) + if isinstance(state.last_updated, datetime) + else self.now ) @callback @@ -2004,7 +2004,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]): if in_cycle: slope = self._window_auto_algo.check_age_last_measurement( temperature=self._ema_temp, - datetime_now=datetime.now(get_tz(self._hass)), + datetime_now=self.now, ) else: slope = self._window_auto_algo.add_temp_measurement( @@ -2296,6 +2296,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]): async def check_safety(self) -> bool: """Check if last temperature date is too long""" + now = self.now delta_temp = ( now - self._last_temperature_measure.replace(tzinfo=self._current_tz) @@ -2663,9 +2664,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]): "device_power": self._device_power, ATTR_MEAN_POWER_CYCLE: self.mean_cycle_power, ATTR_TOTAL_ENERGY: self.total_energy, - "last_update_datetime": datetime.now() - .astimezone(self._current_tz) - .isoformat(), + "last_update_datetime": self.now.isoformat(), "timezone": str(self._current_tz), "temperature_unit": self.temperature_unit, "is_device_active": self.is_device_active, diff --git a/custom_components/versatile_thermostat/climate.py b/custom_components/versatile_thermostat/climate.py index 5b11b2e..941bb8f 100644 --- a/custom_components/versatile_thermostat/climate.py +++ b/custom_components/versatile_thermostat/climate.py @@ -22,28 +22,12 @@ from homeassistant.const import ( STATE_NOT_HOME, ) -from .const import ( - DOMAIN, - PLATFORMS, - CONF_PRESETS_WITH_AC, - SERVICE_SET_PRESENCE, - SERVICE_SET_PRESET_TEMPERATURE, - 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_CENTRAL_CONFIG, - CONF_SONOFF_TRZB_MODE, -) +from .const import * # pylint: disable=wildcard-import,unused-wildcard-import from .thermostat_switch import ThermostatOverSwitch from .thermostat_climate import ThermostatOverClimate from .thermostat_valve import ThermostatOverValve -from .thermostat_sonoff_trvzb import ThermostatOverSonoffTRVZB +from .thermostat_climate_valve import ThermostatOverClimateValve _LOGGER = logging.getLogger(__name__) @@ -62,7 +46,9 @@ async def async_setup_entry( unique_id = entry.entry_id name = entry.data.get(CONF_NAME) vt_type = entry.data.get(CONF_THERMOSTAT_TYPE) - is_sonoff_trvzb = entry.data.get(CONF_SONOFF_TRZB_MODE) + have_valve_regulation = ( + entry.data.get(CONF_AUTO_REGULATION_MODE) == CONF_AUTO_REGULATION_VALVE + ) if vt_type == CONF_THERMOSTAT_CENTRAL_CONFIG: return @@ -72,8 +58,8 @@ async def async_setup_entry( if vt_type == CONF_THERMOSTAT_SWITCH: entity = ThermostatOverSwitch(hass, unique_id, name, entry.data) elif vt_type == CONF_THERMOSTAT_CLIMATE: - if is_sonoff_trvzb is True: - entity = ThermostatOverSonoffTRVZB(hass, unique_id, name, entry.data) + if have_valve_regulation is True: + entity = ThermostatOverClimateValve(hass, unique_id, name, entry.data) else: entity = ThermostatOverClimate(hass, unique_id, name, entry.data) elif vt_type == CONF_THERMOSTAT_VALVE: diff --git a/custom_components/versatile_thermostat/config_flow.py b/custom_components/versatile_thermostat/config_flow.py index 6441d65..024c75a 100644 --- a/custom_components/versatile_thermostat/config_flow.py +++ b/custom_components/versatile_thermostat/config_flow.py @@ -29,27 +29,6 @@ COMES_FROM = "comes_from" _LOGGER = logging.getLogger(__name__) - -# Not used but can be useful in other context -# def schema_defaults(schema, **defaults): -# """Create a new schema with default values filled in.""" -# copy = schema.extend({}) -# for field, field_type in copy.schema.items(): -# if isinstance(field_type, vol.In): -# value = None -# -# if value in field_type.container: -# # field.default = vol.default_factory(value) -# field.description = {"suggested_value": value} -# continue -# -# if field.schema in defaults: -# # field.default = vol.default_factory(defaults[field]) -# field.description = {"suggested_value": defaults[field]} -# return copy -# - - def add_suggested_values_to_schema( data_schema: vol.Schema, suggested_values: Mapping[str, Any] ) -> vol.Schema: @@ -162,17 +141,18 @@ class VersatileThermostatBaseConfigFlow(FlowHandler): if COMES_FROM in self._infos: del self._infos[COMES_FROM] - def check_sonoff_trvzb_nb_entities(self, data: dict) -> bool: - """Check the number of entities for Sonoff TRVZB""" + def is_valve_regulation_selected(self, infos) -> bool: + """True of the valve regulation mode is selected""" + return infos.get(CONF_AUTO_REGULATION_MODE, None) == CONF_AUTO_REGULATION_VALVE + + def check_valve_regulation_nb_entities(self, data: dict) -> bool: + """Check the number of entities for Valve regulation""" ret = True - if ( - self._infos.get(CONF_SONOFF_TRZB_MODE) - and data.get(CONF_OFFSET_CALIBRATION_LIST) is not None - ): + if self.is_valve_regulation_selected(self._infos): nb_unders = len(self._infos.get(CONF_UNDERLYING_LIST)) - nb_offset = len(data.get(CONF_OFFSET_CALIBRATION_LIST)) - nb_opening = len(data.get(CONF_OPENING_DEGREE_LIST)) - nb_closing = len(data.get(CONF_CLOSING_DEGREE_LIST)) + nb_offset = len(data.get(CONF_OFFSET_CALIBRATION_LIST, [])) + nb_opening = len(data.get(CONF_OPENING_DEGREE_LIST, [])) + nb_closing = len(data.get(CONF_CLOSING_DEGREE_LIST, [])) if ( nb_unders != nb_offset or nb_unders != nb_opening @@ -181,7 +161,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler): ret = False return ret - async def validate_input(self, data: dict, step_id) -> None: + async def validate_input(self, data: dict, _) -> None: """Validate the user input allows us to connect. Data has the keys from STEP_*_DATA_SCHEMA with values provided by the user. @@ -259,8 +239,8 @@ class VersatileThermostatBaseConfigFlow(FlowHandler): # Check that the number of offet_calibration and opening_degree and closing_degree are equals # to the number of underlying entities - if not self.check_sonoff_trvzb_nb_entities(data): - raise SonoffTRVZBNbEntitiesIncorrect() + if not self.check_valve_regulation_nb_entities(data): + raise ValveRegulationNbEntitiesIncorrect() def check_config_complete(self, infos) -> bool: """True if the config is now complete (ie all mandatory attributes are set)""" @@ -357,7 +337,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler): ): return False - if not self.check_sonoff_trvzb_nb_entities(infos): + if not self.check_valve_regulation_nb_entities(infos): return False return True @@ -400,8 +380,8 @@ class VersatileThermostatBaseConfigFlow(FlowHandler): errors[str(err)] = "service_configuration_format" except ConfigurationNotCompleteError as err: errors["base"] = "configuration_not_complete" - except SonoffTRVZBNbEntitiesIncorrect as err: - errors["base"] = "sonoff_trvzb_nb_entities_incorrect" + except ValveRegulationNbEntitiesIncorrect as err: + errors["base"] = "valve_regulation_nb_entities_incorrect" except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" @@ -488,8 +468,8 @@ class VersatileThermostatBaseConfigFlow(FlowHandler): ]: menu_options.append("auto_start_stop") - if self._infos.get(CONF_SONOFF_TRZB_MODE) is True: - menu_options.append("sonoff_trvzb") + if self.is_valve_regulation_selected(self._infos): + menu_options.append("valve_regulation") menu_options.append("advanced") @@ -563,7 +543,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler): if ( self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_CLIMATE and user_input is not None - and not user_input.get(CONF_SONOFF_TRZB_MODE) + and not self.is_valve_regulation_selected(user_input) ): # Remove TPI info for key in [ @@ -621,19 +601,21 @@ class VersatileThermostatBaseConfigFlow(FlowHandler): return await self.generic_step("auto_start_stop", schema, user_input, next_step) - async def async_step_sonoff_trvzb( + async def async_step_valve_regulation( self, user_input: dict | None = None ) -> FlowResult: - """Handle the Sonoff TRVZB configuration step""" + """Handle the valve regulation configuration step""" _LOGGER.debug( - "Into ConfigFlow.async_step_sonoff_trvzb user_input=%s", user_input + "Into ConfigFlow.async_step_valve_regulation user_input=%s", user_input ) - schema = STEP_SONOFF_TRVZB + schema = STEP_VALVE_REGULATION self._infos[COMES_FROM] = None next_step = self.async_step_menu - return await self.generic_step("sonoff_trvzb", schema, user_input, next_step) + return await self.generic_step( + "valve_regulation", schema, user_input, next_step + ) async def async_step_tpi(self, user_input: dict | None = None) -> FlowResult: """Handle the TPI flow steps""" diff --git a/custom_components/versatile_thermostat/config_schema.py b/custom_components/versatile_thermostat/config_schema.py index dd0b119..701d44e 100644 --- a/custom_components/versatile_thermostat/config_schema.py +++ b/custom_components/versatile_thermostat/config_schema.py @@ -141,7 +141,6 @@ STEP_THERMOSTAT_CLIMATE = vol.Schema( # pylint: disable=invalid-name selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN, multiple=True), ), vol.Optional(CONF_AC_MODE, default=False): cv.boolean, - vol.Optional(CONF_SONOFF_TRZB_MODE, default=False): cv.boolean, vol.Optional( CONF_AUTO_REGULATION_MODE, default=CONF_AUTO_REGULATION_NONE ): selector.SelectSelector( @@ -198,19 +197,19 @@ STEP_AUTO_START_STOP = vol.Schema( # pylint: disable=invalid-name } ) -STEP_SONOFF_TRVZB = vol.Schema( # pylint: disable=invalid-name +STEP_VALVE_REGULATION = vol.Schema( # pylint: disable=invalid-name { - vol.Required(CONF_OFFSET_CALIBRATION_LIST): selector.EntitySelector( - selector.EntitySelectorConfig( - domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], multiple=True - ), - ), vol.Required(CONF_OPENING_DEGREE_LIST): selector.EntitySelector( selector.EntitySelectorConfig( domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], multiple=True ), ), - vol.Required(CONF_CLOSING_DEGREE_LIST): selector.EntitySelector( + vol.Optional(CONF_OFFSET_CALIBRATION_LIST): selector.EntitySelector( + selector.EntitySelectorConfig( + domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], multiple=True + ), + ), + vol.Optional(CONF_CLOSING_DEGREE_LIST): selector.EntitySelector( selector.EntitySelectorConfig( domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], multiple=True ), diff --git a/custom_components/versatile_thermostat/const.py b/custom_components/versatile_thermostat/const.py index f00fb09..06a3185 100644 --- a/custom_components/versatile_thermostat/const.py +++ b/custom_components/versatile_thermostat/const.py @@ -526,8 +526,8 @@ class ConfigurationNotCompleteError(HomeAssistantError): """Error the configuration is not complete""" -class SonoffTRVZBNbEntitiesIncorrect(HomeAssistantError): - """Error to indicate there is an error in the configuration of the Sonoff TRVZB. +class ValveRegulationNbEntitiesIncorrect(HomeAssistantError): + """Error to indicate there is an error in the configuration of the TRV with valve regulation. The number of specific entities is incorrect.""" diff --git a/custom_components/versatile_thermostat/sensor.py b/custom_components/versatile_thermostat/sensor.py index 183b725..344315a 100644 --- a/custom_components/versatile_thermostat/sensor.py +++ b/custom_components/versatile_thermostat/sensor.py @@ -49,7 +49,8 @@ from .const import ( CONF_THERMOSTAT_TYPE, CONF_THERMOSTAT_CENTRAL_CONFIG, CONF_USE_CENTRAL_BOILER_FEATURE, - CONF_SONOFF_TRZB_MODE, + CONF_AUTO_REGULATION_VALVE, + CONF_AUTO_REGULATION_MODE, overrides, ) @@ -71,7 +72,9 @@ async def async_setup_entry( unique_id = entry.entry_id name = entry.data.get(CONF_NAME) vt_type = entry.data.get(CONF_THERMOSTAT_TYPE) - is_sonoff_trvzb = entry.data.get(CONF_SONOFF_TRZB_MODE) + have_valve_regulation = ( + entry.data.get(CONF_AUTO_REGULATION_MODE) == CONF_AUTO_REGULATION_VALVE + ) entities = None @@ -102,13 +105,13 @@ async def async_setup_entry( if ( entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_VALVE - or is_sonoff_trvzb + or have_valve_regulation ): entities.append(ValveOpenPercentSensor(hass, unique_id, name, entry.data)) if ( entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CLIMATE - and not is_sonoff_trvzb + and not have_valve_regulation ): entities.append( RegulatedTemperatureSensor(hass, unique_id, name, entry.data) diff --git a/custom_components/versatile_thermostat/strings.json b/custom_components/versatile_thermostat/strings.json index b6b0862..d989f3d 100644 --- a/custom_components/versatile_thermostat/strings.json +++ b/custom_components/versatile_thermostat/strings.json @@ -28,7 +28,7 @@ "presence": "Presence detection", "advanced": "Advanced parameters", "auto_start_stop": "Auto start and stop", - "sonoff_trvzb": "Sonoff TRVZB configuration", + "valve_regulation": "Valve regulation configuration", "finalize": "All done", "configuration_not_complete": "Configuration not complete" } @@ -65,7 +65,7 @@ "use_motion_feature": "Use motion detection", "use_power_feature": "Use power management", "use_presence_feature": "Use presence detection", - "use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after seecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page", + "use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after selecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page", "use_auto_start_stop_feature": "Use the auto start and stop feature" } }, @@ -77,7 +77,6 @@ "heater_keep_alive": "Switch keep-alive interval in seconds", "proportional_function": "Algorithm", "ac_mode": "AC mode", - "sonoff_trvzb_mode": "SONOFF TRVZB mode", "auto_regulation_mode": "Self-regulation", "auto_regulation_dtemp": "Regulation threshold", "auto_regulation_periode_min": "Regulation minimum period", @@ -90,7 +89,6 @@ "heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.", "proportional_function": "Algorithm to use (TPI is the only one for now)", "ac_mode": "Use the Air Conditioning (AC) mode", - "sonoff_trvzb_mode": "The underlyings are SONOFF TRVZB. You have to configure some extra entities in the specific menu option 'Sonoff trvzb configuration'", "auto_regulation_mode": "Auto adjustment of the target temperature", "auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent", "auto_regulation_periode_min": "Duration in minutes between two regulation update", @@ -219,9 +217,9 @@ "central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]" } }, - "sonoff_trvzb": { - "title": "Sonoff TRVZB configuration", - "description": "Specific Sonoff TRVZB configuration", + "valve_regulation": { + "title": "Self-regulation with valve", + "description": "Configuration for self-regulation with direct control of the valve", "data": { "offset_calibration_entity_ids": "Offset calibration entities", "opening_degree_entity_ids": "Opening degree entities", @@ -229,9 +227,9 @@ "proportional_function": "Algorithm" }, "data_description": { - "offset_calibration_entity_ids": "The list of the 'offset calibration' entities. There should be one per underlying climate entities", + "offset_calibration_entity_ids": "The list of the 'offset calibration' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities", "opening_degree_entity_ids": "The list of the 'opening degree' entities. There should be one per underlying climate entities", - "closing_degree_entity_ids": "The list of the 'closing degree' entities. There should be one per underlying climate entities", + "closing_degree_entity_ids": "The list of the 'closing degree' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities", "proportional_function": "Algorithm to use (TPI is the only one for now)" } } @@ -274,7 +272,7 @@ "presence": "Presence detection", "advanced": "Advanced parameters", "auto_start_stop": "Auto start and stop", - "sonoff_trvzb": "Sonoff TRVZB configuration", + "valve_regulation": "Valve regulation configuration", "finalize": "All done", "configuration_not_complete": "Configuration not complete" } @@ -311,7 +309,7 @@ "use_motion_feature": "Use motion detection", "use_power_feature": "Use power management", "use_presence_feature": "Use presence detection", - "use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after seecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page", + "use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after selecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page", "use_auto_start_stop_feature": "Use the auto start and stop feature" } }, @@ -323,7 +321,6 @@ "heater_keep_alive": "Switch keep-alive interval in seconds", "proportional_function": "Algorithm", "ac_mode": "AC mode", - "sonoff_trvzb_mode": "SONOFF TRVZB mode", "auto_regulation_mode": "Self-regulation", "auto_regulation_dtemp": "Regulation threshold", "auto_regulation_periode_min": "Regulation minimum period", @@ -336,7 +333,6 @@ "heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.", "proportional_function": "Algorithm to use (TPI is the only one for now)", "ac_mode": "Use the Air Conditioning (AC) mode", - "sonoff_trvzb_mode": "The underlyings are SONOFF TRVZB. You have to configure some extra entities in the specific menu option 'Sonoff trvzb configuration'", "auto_regulation_mode": "Auto adjustment of the target temperature", "auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent", "auto_regulation_periode_min": "Duration in minutes between two regulation update", @@ -465,9 +461,9 @@ "central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]" } }, - "sonoff_trvzb": { - "title": "Sonoff TRVZB configuration - {name}", - "description": "Specific Sonoff TRVZB configuration", + "valve_regulation": { + "title": "Self-regulation with valve - {name}", + "description": "Configuration for self-regulation with direct control of the valve", "data": { "offset_calibration_entity_ids": "Offset calibration entities", "opening_degree_entity_ids": "Opening degree entities", @@ -475,9 +471,9 @@ "proportional_function": "Algorithm" }, "data_description": { - "offset_calibration_entity_ids": "The list of the 'offset calibration' entities. There should be one per underlying climate entities", + "offset_calibration_entity_ids": "The list of the 'offset calibration' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities", "opening_degree_entity_ids": "The list of the 'opening degree' entities. There should be one per underlying climate entities", - "closing_degree_entity_ids": "The list of the 'closing degree' entities. There should be one per underlying climate entities", + "closing_degree_entity_ids": "The list of the 'closing degree' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities", "proportional_function": "Algorithm to use (TPI is the only one for now)" } } @@ -488,7 +484,7 @@ "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", "no_central_config": "You cannot check 'use central configuration' because no central configuration was found. You need to create a Versatile Thermostat of type 'Central Configuration' to use it.", "service_configuration_format": "The format of the service configuration is wrong", - "sonoff_trvzb_nb_entities_incorrect": "The number of specific entities for Sonoff TRVZB should be equal to the number of underlyings" + "valve_regulation_nb_entities_incorrect": "The number of valve entities for valve regulation should be equal to the number of underlyings" }, "abort": { "already_configured": "Device is already configured" @@ -510,7 +506,8 @@ "auto_regulation_medium": "Medium", "auto_regulation_light": "Light", "auto_regulation_expert": "Expert", - "auto_regulation_none": "No auto-regulation" + "auto_regulation_none": "No auto-regulation", + "auto_regulation_valve": "Direct control of valve" } }, "auto_fan_mode": { diff --git a/custom_components/versatile_thermostat/thermostat_climate.py b/custom_components/versatile_thermostat/thermostat_climate.py index f9bbdca..5941eed 100644 --- a/custom_components/versatile_thermostat/thermostat_climate.py +++ b/custom_components/versatile_thermostat/thermostat_climate.py @@ -1258,6 +1258,13 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]): self.choose_auto_regulation_mode(CONF_AUTO_REGULATION_SLOW) elif auto_regulation_mode == "Expert": self.choose_auto_regulation_mode(CONF_AUTO_REGULATION_EXPERT) + else: + _LOGGER.warning( + "%s - auto_regulation_mode %s is not supported", + self, + auto_regulation_mode, + ) + return await self._send_regulated_temperature() self.update_custom_attributes() diff --git a/custom_components/versatile_thermostat/thermostat_sonoff_trvzb.py b/custom_components/versatile_thermostat/thermostat_climate_valve.py similarity index 86% rename from custom_components/versatile_thermostat/thermostat_sonoff_trvzb.py rename to custom_components/versatile_thermostat/thermostat_climate_valve.py index b1996bb..f07d314 100644 --- a/custom_components/versatile_thermostat/thermostat_sonoff_trvzb.py +++ b/custom_components/versatile_thermostat/thermostat_climate_valve.py @@ -1,5 +1,5 @@ # pylint: disable=line-too-long, too-many-lines, abstract-method -""" A climate over Sonoff TRVZB classe """ +""" A climate with a direct valve regulation class """ import logging from datetime import datetime @@ -7,7 +7,7 @@ from datetime import datetime from homeassistant.core import HomeAssistant from homeassistant.components.climate import HVACMode, HVACAction -from .underlyings import UnderlyingSonoffTRVZB +from .underlyings import UnderlyingValveRegulation # from .commons import NowClass, round_to_nearest from .base_thermostat import ConfigData @@ -21,14 +21,14 @@ from .const import * # pylint: disable=wildcard-import, unused-wildcard-import _LOGGER = logging.getLogger(__name__) -class ThermostatOverSonoffTRVZB(ThermostatOverClimate): - """This class represent a VTherm over a Sonoff TRVZB climate""" +class ThermostatOverClimateValve(ThermostatOverClimate): + """This class represent a VTherm over a climate with a direct valve regulation""" _entity_component_unrecorded_attributes = ThermostatOverClimate._entity_component_unrecorded_attributes.union( # pylint: disable=protected-access frozenset( { "is_over_climate", - "is_over_sonoff_trvzb", + "have_valve_regulation", "underlying_entities", "on_time_sec", "off_time_sec", @@ -40,7 +40,7 @@ class ThermostatOverSonoffTRVZB(ThermostatOverClimate): } ) ) - _underlyings_sonoff_trvzb: list[UnderlyingSonoffTRVZB] = [] + _underlyings_valve_regulation: list[UnderlyingValveRegulation] = [] _valve_open_percent: int | None = None _last_calculation_timestamp: datetime | None = None _auto_regulation_dpercent: float | None = None @@ -49,8 +49,8 @@ class ThermostatOverSonoffTRVZB(ThermostatOverClimate): def __init__( self, hass: HomeAssistant, unique_id: str, name: str, entry_infos: ConfigData ): - """Initialize the ThermostatOverSonoffTRVZB class""" - _LOGGER.debug("%s - creating a ThermostatOverSonoffTRVZB VTherm", name) + """Initialize the ThermostatOverClimateValve class""" + _LOGGER.debug("%s - creating a ThermostatOverClimateValve VTherm", name) super().__init__(hass, unique_id, name, entry_infos) # self._valve_open_percent: int = 0 # self._last_calculation_timestamp: datetime | None = None @@ -60,8 +60,8 @@ class ThermostatOverSonoffTRVZB(ThermostatOverClimate): @overrides def post_init(self, config_entry: ConfigData): """Initialize the Thermostat and underlyings - Beware that the underlyings list contains the climate which represent the Sonoff TRVZB - but also the UnderlyingSonoff which reprensent the valve""" + Beware that the underlyings list contains the climate which represent the TRV + but also the UnderlyingValveRegulation which reprensent the valve""" super().post_init(config_entry) @@ -90,7 +90,7 @@ class ThermostatOverSonoffTRVZB(ThermostatOverClimate): offset = config_entry.get(CONF_OFFSET_CALIBRATION_LIST)[idx] opening = config_entry.get(CONF_OPENING_DEGREE_LIST)[idx] closing = config_entry.get(CONF_CLOSING_DEGREE_LIST)[idx] - under = UnderlyingSonoffTRVZB( + under = UnderlyingValveRegulation( hass=self._hass, thermostat=self, offset_calibration_entity_id=offset, @@ -98,19 +98,19 @@ class ThermostatOverSonoffTRVZB(ThermostatOverClimate): closing_degree_entity_id=closing, climate_underlying=self._underlyings[idx], ) - self._underlyings_sonoff_trvzb.append(under) + self._underlyings_valve_regulation.append(under) @overrides def update_custom_attributes(self): """Custom attributes""" super().update_custom_attributes() - self._attr_extra_state_attributes["is_over_sonoff_trvzb"] = ( - self.is_over_sonoff_trvzb + self._attr_extra_state_attributes["have_valve_regulation"] = ( + self.have_valve_regulation ) - self._attr_extra_state_attributes["underlying_sonoff_trvzb_entities"] = [ - underlying.entity_id for underlying in self._underlyings_sonoff_trvzb + self._attr_extra_state_attributes["underlyings_valve_regulation"] = [ + underlying.entity_id for underlying in self._underlyings_valve_regulation ] self._attr_extra_state_attributes["on_percent"] = ( @@ -233,12 +233,12 @@ class ThermostatOverSonoffTRVZB(ThermostatOverClimate): self._attr_min_temp, ) - for under in self._underlyings_sonoff_trvzb: + for under in self._underlyings_valve_regulation: await under.set_valve_open_percent() @property - def is_over_sonoff_trvzb(self) -> bool: - """True if the Thermostat is over_sonoff_trvzb""" + def have_valve_regulation(self) -> bool: + """True if the Thermostat is regulated by valve""" return True @property @@ -264,11 +264,16 @@ class ThermostatOverSonoffTRVZB(ThermostatOverClimate): @property def hvac_action(self) -> HVACAction | None: - """Returns the current hvac_action by checking all hvac_action of the _underlyings_sonoff_trvzb""" + """Returns the current hvac_action by checking all hvac_action of the _underlyings_valve_regulation""" - return self.calculate_hvac_action(self._underlyings_sonoff_trvzb) + return self.calculate_hvac_action(self._underlyings_valve_regulation) @property def is_device_active(self) -> bool: """A hack to overrides the state from underlyings""" return self.valve_open_percent > 0 + + @overrides + async def service_set_auto_regulation_mode(self, auto_regulation_mode: str): + """This should not be possible in valve regulation mode""" + return diff --git a/custom_components/versatile_thermostat/translations/en.json b/custom_components/versatile_thermostat/translations/en.json index 952577a..d989f3d 100644 --- a/custom_components/versatile_thermostat/translations/en.json +++ b/custom_components/versatile_thermostat/translations/en.json @@ -28,7 +28,7 @@ "presence": "Presence detection", "advanced": "Advanced parameters", "auto_start_stop": "Auto start and stop", - "sonoff_trvzb": "Sonoff TRVZB configuration", + "valve_regulation": "Valve regulation configuration", "finalize": "All done", "configuration_not_complete": "Configuration not complete" } @@ -77,7 +77,6 @@ "heater_keep_alive": "Switch keep-alive interval in seconds", "proportional_function": "Algorithm", "ac_mode": "AC mode", - "sonoff_trvzb_mode": "SONOFF TRVZB mode", "auto_regulation_mode": "Self-regulation", "auto_regulation_dtemp": "Regulation threshold", "auto_regulation_periode_min": "Regulation minimum period", @@ -90,7 +89,6 @@ "heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.", "proportional_function": "Algorithm to use (TPI is the only one for now)", "ac_mode": "Use the Air Conditioning (AC) mode", - "sonoff_trvzb_mode": "The underlyings are SONOFF TRVZB. You have to configure some extra entities in the specific menu option 'Sonoff trvzb configuration'", "auto_regulation_mode": "Auto adjustment of the target temperature", "auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent", "auto_regulation_periode_min": "Duration in minutes between two regulation update", @@ -219,9 +217,9 @@ "central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]" } }, - "sonoff_trvzb": { - "title": "Sonoff TRVZB configuration", - "description": "Specific Sonoff TRVZB configuration", + "valve_regulation": { + "title": "Self-regulation with valve", + "description": "Configuration for self-regulation with direct control of the valve", "data": { "offset_calibration_entity_ids": "Offset calibration entities", "opening_degree_entity_ids": "Opening degree entities", @@ -229,9 +227,9 @@ "proportional_function": "Algorithm" }, "data_description": { - "offset_calibration_entity_ids": "The list of the 'offset calibration' entities. There should be one per underlying climate entities", + "offset_calibration_entity_ids": "The list of the 'offset calibration' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities", "opening_degree_entity_ids": "The list of the 'opening degree' entities. There should be one per underlying climate entities", - "closing_degree_entity_ids": "The list of the 'closing degree' entities. There should be one per underlying climate entities", + "closing_degree_entity_ids": "The list of the 'closing degree' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities", "proportional_function": "Algorithm to use (TPI is the only one for now)" } } @@ -274,7 +272,7 @@ "presence": "Presence detection", "advanced": "Advanced parameters", "auto_start_stop": "Auto start and stop", - "sonoff_trvzb": "Sonoff TRVZB configuration", + "valve_regulation": "Valve regulation configuration", "finalize": "All done", "configuration_not_complete": "Configuration not complete" } @@ -311,7 +309,7 @@ "use_motion_feature": "Use motion detection", "use_power_feature": "Use power management", "use_presence_feature": "Use presence detection", - "use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after seecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page", + "use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after selecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page", "use_auto_start_stop_feature": "Use the auto start and stop feature" } }, @@ -323,7 +321,6 @@ "heater_keep_alive": "Switch keep-alive interval in seconds", "proportional_function": "Algorithm", "ac_mode": "AC mode", - "sonoff_trvzb_mode": "SONOFF TRVZB mode", "auto_regulation_mode": "Self-regulation", "auto_regulation_dtemp": "Regulation threshold", "auto_regulation_periode_min": "Regulation minimum period", @@ -336,7 +333,6 @@ "heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.", "proportional_function": "Algorithm to use (TPI is the only one for now)", "ac_mode": "Use the Air Conditioning (AC) mode", - "sonoff_trvzb_mode": "The underlyings are SONOFF TRVZB. You have to configure some extra entities in the specific menu option 'Sonoff trvzb configuration'", "auto_regulation_mode": "Auto adjustment of the target temperature", "auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent", "auto_regulation_periode_min": "Duration in minutes between two regulation update", @@ -454,7 +450,7 @@ } }, "central_boiler": { - "title": "Control of the central boiler", + "title": "Control of the central boiler - {name}", "description": "Enter the services to call to turn on/off the central boiler. Leave blank if no service call is to be made (in this case, you will have to manage the turning on/off of your central boiler yourself). The service called must be formatted as follows: `entity_id/service_name[/attribute:value]` (/attribute:value is optional)\nFor example:\n- to turn on a switch: `switch.controle_chaudiere/switch.turn_on`\n- to turn off a switch: `switch.controle_chaudiere/switch.turn_off`\n- to program the boiler to 25° and thus force its ignition: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- to send 10° to the boiler and thus force its extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`", "data": { "central_boiler_activation_service": "Command to turn-on", @@ -465,9 +461,9 @@ "central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]" } }, - "sonoff_trvzb": { - "title": "Sonoff TRVZB configuration", - "description": "Specific Sonoff TRVZB configuration", + "valve_regulation": { + "title": "Self-regulation with valve - {name}", + "description": "Configuration for self-regulation with direct control of the valve", "data": { "offset_calibration_entity_ids": "Offset calibration entities", "opening_degree_entity_ids": "Opening degree entities", @@ -475,9 +471,9 @@ "proportional_function": "Algorithm" }, "data_description": { - "offset_calibration_entity_ids": "The list of the 'offset calibration' entities. There should be one per underlying climate entities", + "offset_calibration_entity_ids": "The list of the 'offset calibration' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities", "opening_degree_entity_ids": "The list of the 'opening degree' entities. There should be one per underlying climate entities", - "closing_degree_entity_ids": "The list of the 'closing degree' entities. There should be one per underlying climate entities", + "closing_degree_entity_ids": "The list of the 'closing degree' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities", "proportional_function": "Algorithm to use (TPI is the only one for now)" } } @@ -488,7 +484,7 @@ "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", "no_central_config": "You cannot check 'use central configuration' because no central configuration was found. You need to create a Versatile Thermostat of type 'Central Configuration' to use it.", "service_configuration_format": "The format of the service configuration is wrong", - "sonoff_trvzb_nb_entities_incorrect": "The number of specific entities for Sonoff TRVZB should be equal to the number of underlyings" + "valve_regulation_nb_entities_incorrect": "The number of valve entities for valve regulation should be equal to the number of underlyings" }, "abort": { "already_configured": "Device is already configured" @@ -510,7 +506,8 @@ "auto_regulation_medium": "Medium", "auto_regulation_light": "Light", "auto_regulation_expert": "Expert", - "auto_regulation_none": "No auto-regulation" + "auto_regulation_none": "No auto-regulation", + "auto_regulation_valve": "Direct control of valve" } }, "auto_fan_mode": { diff --git a/custom_components/versatile_thermostat/translations/fr.json b/custom_components/versatile_thermostat/translations/fr.json index 432a146..cc90149 100644 --- a/custom_components/versatile_thermostat/translations/fr.json +++ b/custom_components/versatile_thermostat/translations/fr.json @@ -28,7 +28,7 @@ "presence": "Détection de présence", "advanced": "Paramètres avancés", "auto_start_stop": "Allumage/extinction automatique", - "sonoff_trvzb": "Configuration spécifique à Sonoff TRVZB", + "valve_regulation": "Configuration de la regulation par vanne", "finalize": "Finaliser la création", "configuration_not_complete": "Configuration incomplète" } @@ -77,7 +77,6 @@ "heater_keep_alive": "keep-alive (sec)", "proportional_function": "Algorithme", "ac_mode": "AC mode ?", - "sonoff_trvzb_mode": "Mode Sonoff TRVZB", "auto_regulation_mode": "Auto-régulation", "auto_regulation_dtemp": "Seuil de régulation", "auto_regulation_periode_min": "Période minimale de régulation", @@ -90,9 +89,8 @@ "heater_keep_alive": "Intervalle de rafraichissement du switch en secondes. Laisser vide pour désactiver. À n'utiliser que pour les switchs qui le nécessite.", "proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)", "ac_mode": "Utilisation du mode Air Conditionné (AC)", - "sonoff_trvzb_mode": "Les équipements sont des Sonoff TRVZB. Vous devez configurer les entités dédiées dans le menu 'Configuration Sonoff TRVZB'", - "auto_regulation_mode": "Ajustement automatique de la température cible", - "auto_regulation_dtemp": "Le seuil en ° (ou % pour les valves) en-dessous duquel la régulation ne sera pas envoyée", + "auto_regulation_mode": "Utilisation de l'auto-régulation faite par VTherm", + "auto_regulation_dtemp": "Le seuil en ° (ou % pour les vannes) en-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", "auto_regulation_use_device_temp": "Compenser la temperature interne du sous-jacent pour accélérer l'auto-régulation", "inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode", @@ -219,19 +217,19 @@ "central_boiler_deactivation_service": "Commande à éxecuter pour étiendre la chaudière centrale au format entity_id/service_name[/attribut:valeur]" } }, - "sonoff_trvzb": { - "title": "Configuration Sonoff TRVZB", - "description": "Configuration spécifique des Sonoff TRVZB", + "valve_regulation": { + "title": "Auto-régulation par vanne - {name}", + "description": "Configuration de l'auto-régulation par controle direct de la vanne", "data": { - "offset_calibration_entity_ids": "Entités de 'Offset calibration'", - "opening_degree_entity_ids": "Entités de 'Opening degree'", - "closing_degree_entity_ids": "Entités de 'Closing degree'", + "offset_calibration_entity_ids": "Entités de 'calibrage du décalage''", + "opening_degree_entity_ids": "Entités 'ouverture de vanne'", + "closing_degree_entity_ids": "Entités 'fermeture de la vanne'", "proportional_function": "Algorithme" }, "data_description": { - "offset_calibration_entity_ids": "La liste des entités 'offset calibration' entities. Il doit y en avoir une par entité climate sous-jacente", - "opening_degree_entity_ids": "La liste des entités 'opening degree' entities. Il doit y en avoir une par entité climate sous-jacente", - "closing_degree_entity_ids": "La liste des entités 'closing degree' entities. Il doit y en avoir une par entité climate sous-jacente", + "offset_calibration_entity_ids": "La liste des entités 'calibrage du décalage' (offset calibration). Configurez le si votre TRV possède cette fonction pour une meilleure régulation. Il doit y en avoir une par entité climate sous-jacente", + "opening_degree_entity_ids": "La liste des entités 'ouverture de vanne'. Il doit y en avoir une par entité climate sous-jacente", + "closing_degree_entity_ids": "La liste des entités 'fermeture de la vanne'. Configurez le si votre TRV possède cette fonction pour une meilleure régulation. Il doit y en avoir une par entité climate sous-jacente", "proportional_function": "Algorithme à utiliser (seulement TPI est disponible)" } } @@ -274,7 +272,7 @@ "presence": "Détection de présence", "advanced": "Paramètres avancés", "auto_start_stop": "Allumage/extinction automatique", - "sonoff_trvzb": "Configuration spécifique à Sonoff TRVZB", + "valve_regulation": "Configuration de la regulation par vanne", "finalize": "Finaliser les modifications", "configuration_not_complete": "Configuration incomplète" } @@ -323,7 +321,6 @@ "heater_keep_alive": "keep-alive (sec)", "proportional_function": "Algorithme", "ac_mode": "AC mode ?", - "sonoff_trvzb_mode": "Mode Sonoff TRVZB", "auto_regulation_mode": "Auto-régulation", "auto_regulation_dtemp": "Seuil de régulation", "auto_regulation_periode_min": "Période minimale de régulation", @@ -336,9 +333,8 @@ "heater_keep_alive": "Intervalle de rafraichissement du switch en secondes. Laisser vide pour désactiver. À n'utiliser que pour les switchs qui le nécessite.", "proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)", "ac_mode": "Utilisation du mode Air Conditionné (AC)", - "sonoff_trvzb_mode": "Les équipements sont des Sonoff TRVZB. Vous devez configurer les entités dédiées dans le menu 'Configuration Sonoff TRVZB'", - "auto_regulation_mode": "Ajustement automatique de la température cible", - "auto_regulation_dtemp": "Le seuil en ° (ou % pour les valves) en-dessous duquel la régulation ne sera pas envoyée", + "auto_regulation_mode": "Utilisation de l'auto-régulation faite par VTherm", + "auto_regulation_dtemp": "Le seuil en ° (ou % pour les vannes) en-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", "auto_regulation_use_device_temp": "Compenser la temperature interne du sous-jacent pour accélérer l'auto-régulation", "inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode", @@ -459,19 +455,19 @@ "central_boiler_deactivation_service": "Commande à éxecuter pour étiendre la chaudière centrale au format entity_id/service_name[/attribut:valeur]" } }, - "sonoff_trvzb": { - "title": "Configuration Sonoff TRVZB - {name}", - "description": "Configuration spécifique des Sonoff TRVZB", + "valve_regulation": { + "title": "Auto-régulation par vanne - {name}", + "description": "Configuration de l'auto-régulation par controle direct de la vanne", "data": { - "offset_calibration_entity_ids": "Entités de 'Offset calibration'", - "opening_degree_entity_ids": "Entités de 'Opening degree'", - "closing_degree_entity_ids": "Entités de 'Closing degree'", + "offset_calibration_entity_ids": "Entités de 'calibrage du décalage''", + "opening_degree_entity_ids": "Entités 'ouverture de vanne'", + "closing_degree_entity_ids": "Entités 'fermeture de la vanne'", "proportional_function": "Algorithme" }, "data_description": { - "offset_calibration_entity_ids": "La liste des entités 'offset calibration' entities. Il doit y en avoir une par entité climate sous-jacente", - "opening_degree_entity_ids": "La liste des entités 'opening degree' entities. Il doit y en avoir une par entité climate sous-jacente", - "closing_degree_entity_ids": "La liste des entités 'closing degree' entities. Il doit y en avoir une par entité climate sous-jacente", + "offset_calibration_entity_ids": "La liste des entités 'calibrage du décalage' (offset calibration). Configurez le si votre TRV possède cette fonction pour une meilleure régulation. Il doit y en avoir une par entité climate sous-jacente", + "opening_degree_entity_ids": "La liste des entités 'ouverture de vanne'. Il doit y en avoir une par entité climate sous-jacente", + "closing_degree_entity_ids": "La liste des entités 'fermeture de la vanne'. Configurez le si votre TRV possède cette fonction pour une meilleure régulation. Il doit y en avoir une par entité climate sous-jacente", "proportional_function": "Algorithme à utiliser (seulement TPI est disponible)" } } @@ -482,7 +478,7 @@ "window_open_detection_method": "Une seule méthode de détection des ouvertures ouvertes doit être utilisée. Utilisez le détecteur d'ouverture ou les seuils de température mais pas les deux.", "no_central_config": "Vous ne pouvez pas cocher 'Utiliser la configuration centrale' car aucune configuration centrale n'a été trouvée. Vous devez créer un Versatile Thermostat de type 'Central Configuration' pour pouvoir l'utiliser.", "service_configuration_format": "Mauvais format de la configuration du service", - "sonoff_trvzb_nb_entities_incorrect": "Le nombre d'entités spécifiques au Sonoff TRVZB doit être égal au nombre d'entité sous-jacentes" + "valve_regulation_nb_entities_incorrect": "Le nombre d'entités pour la régulation par vanne doit être égal au nombre d'entité sous-jacentes" }, "abort": { "already_configured": "Le device est déjà configuré" @@ -504,7 +500,8 @@ "auto_regulation_medium": "Moyenne", "auto_regulation_light": "Légère", "auto_regulation_expert": "Expert", - "auto_regulation_none": "Aucune" + "auto_regulation_none": "Aucune", + "auto_regulation_valve": "Contrôle direct de la vanne" } }, "auto_fan_mode": { diff --git a/custom_components/versatile_thermostat/underlyings.py b/custom_components/versatile_thermostat/underlyings.py index e40d8df..40691c4 100644 --- a/custom_components/versatile_thermostat/underlyings.py +++ b/custom_components/versatile_thermostat/underlyings.py @@ -53,8 +53,8 @@ class UnderlyingEntityType(StrEnum): # a valve VALVE = "valve" - # a Sonoff TRVZB - SONOFF_TRVZB = "sonoff_trvzb" + # a direct valve regulation + VALVE_REGULATION = "valve_regulation" class UnderlyingEntity: @@ -871,7 +871,11 @@ class UnderlyingValve(UnderlyingEntity): _last_sent_temperature = None def __init__( - self, hass: HomeAssistant, thermostat: Any, valve_entity_id: str + self, + hass: HomeAssistant, + thermostat: Any, + valve_entity_id: str, + type: UnderlyingEntityType = UnderlyingEntityType.VALVE, ) -> None: """Initialize the underlying valve""" @@ -1014,8 +1018,8 @@ class UnderlyingValve(UnderlyingEntity): self._cancel_cycle() -class UnderlyingSonoffTRVZB(UnderlyingValve): - """A specific underlying class for Sonoff TRVZB TRV""" +class UnderlyingValveRegulation(UnderlyingValve): + """A specific underlying class for Valve regulation""" _offset_calibration_entity_id: str _opening_degree_entity_id: str @@ -1030,8 +1034,13 @@ class UnderlyingSonoffTRVZB(UnderlyingValve): closing_degree_entity_id: str, climate_underlying: UnderlyingClimate, ) -> None: - """Initialize the underlying Sonoff TRV""" - super().__init__(hass, thermostat, opening_degree_entity_id) + """Initialize the underlying TRV with valve regulation""" + super().__init__( + hass, + thermostat, + opening_degree_entity_id, + type=UnderlyingEntityType.VALVE_REGULATION, + ) self._offset_calibration_entity_id = offset_calibration_entity_id self._opening_degree_entity_id = opening_degree_entity_id self._closing_degree_entity_id = closing_degree_entity_id @@ -1050,17 +1059,21 @@ class UnderlyingSonoffTRVZB(UnderlyingValve): self._max_opening_degree = self._hass.states.get( self._opening_degree_entity_id ).attributes.get("max") - self._min_offset_calibration = self._hass.states.get( - self._offset_calibration_entity_id - ).attributes.get("min") - self._max_offset_calibration = self._hass.states.get( - self._offset_calibration_entity_id - ).attributes.get("max") - self._is_min_max_initialized = ( - self._max_opening_degree is not None - and self._min_offset_calibration is not None - and self._max_offset_calibration is not None + if self.have_offset_calibration_entity: + self._min_offset_calibration = self._hass.states.get( + self._offset_calibration_entity_id + ).attributes.get("min") + self._max_offset_calibration = self._hass.states.get( + self._offset_calibration_entity_id + ).attributes.get("max") + + self._is_min_max_initialized = self._max_opening_degree is not None and ( + not self.have_offset_calibration_entity + or ( + self._min_offset_calibration is not None + and self._max_offset_calibration is not None + ) ) if not self._is_min_max_initialized: @@ -1074,7 +1087,7 @@ class UnderlyingSonoffTRVZB(UnderlyingValve): # Send closing_degree if set closing_degree = None - if self._closing_degree_entity_id is not None: + if self.have_closing_degree_entity: await self._send_value_to_number( self._closing_degree_entity_id, closing_degree := self._max_opening_degree - self._percent_open, @@ -1082,7 +1095,7 @@ class UnderlyingSonoffTRVZB(UnderlyingValve): # send offset_calibration to the difference between target temp and local temp offset = None - if self._offset_calibration_entity_id is not None: + if self.have_offset_calibration_entity: if ( (local_temp := self._climate_underlying.underlying_current_temperature) is not None @@ -1107,7 +1120,7 @@ class UnderlyingSonoffTRVZB(UnderlyingValve): ) _LOGGER.debug( - "%s - SonoffTRVZB - I have sent offset_calibration=%s opening_degree=%s closing_degree=%s", + "%s - valve regulation - I have sent offset_calibration=%s opening_degree=%s closing_degree=%s", self, offset, self._percent_open, @@ -1129,6 +1142,16 @@ class UnderlyingSonoffTRVZB(UnderlyingValve): """The offset_calibration_entity_id""" return self._closing_degree_entity_id + @property + def have_closing_degree_entity(self) -> bool: + """Return True if the underlying have a closing_degree entity""" + return self._closing_degree_entity_id is not None + + @property + def have_offset_calibration_entity(self) -> bool: + """Return True if the underlying have a offset_calibration entity""" + return self._offset_calibration_entity_id is not None + @property def hvac_modes(self) -> list[HVACMode]: """Get the hvac_modes""" diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index dff9a4b..448dd79 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -1,7 +1,6 @@ -# pylint: disable=unused-argument, line-too-long +# pylint: disable=unused-argument, line-too-long, too-many-lines """ Test the Versatile Thermostat config flow """ -from homeassistant import data_entry_flow from homeassistant.data_entry_flow import FlowResultType from homeassistant.core import HomeAssistant from homeassistant.config_entries import SOURCE_USER, ConfigEntry @@ -517,7 +516,7 @@ async def test_user_config_flow_over_climate( CONF_USE_ADVANCED_CENTRAL_CONFIG: False, CONF_USED_BY_CENTRAL_BOILER: False, CONF_USE_CENTRAL_MODE: False, - CONF_SONOFF_TRZB_MODE: False, + CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_STRONG, } assert result["result"] assert result["result"].domain == DOMAIN @@ -1127,7 +1126,7 @@ async def test_user_config_flow_over_climate_auto_start_stop( CONF_USED_BY_CENTRAL_BOILER: False, CONF_USE_AUTO_START_STOP_FEATURE: True, CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_MEDIUM, - CONF_SONOFF_TRZB_MODE: False, + CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_STRONG, } assert result["result"] assert result["result"].domain == DOMAIN