Step 2 - renaming. All tests ok

This commit is contained in:
Jean-Marc Collin
2024-11-23 10:08:57 +00:00
parent ffb976cfa1
commit 9abea3d198
13 changed files with 200 additions and 206 deletions
@@ -460,8 +460,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
else DEFAULT_SECURITY_DEFAULT_ON_PERCENT else DEFAULT_SECURITY_DEFAULT_ON_PERCENT
) )
self._minimal_activation_delay = entry_infos.get(CONF_MINIMAL_ACTIVATION_DELAY) self._minimal_activation_delay = entry_infos.get(CONF_MINIMAL_ACTIVATION_DELAY)
self._last_temperature_measure = datetime.now(tz=self._current_tz) self._last_temperature_measure = self.now
self._last_ext_temperature_measure = datetime.now(tz=self._current_tz) self._last_ext_temperature_measure = self.now
self._security_state = False self._security_state = False
# Initiate the ProportionalAlgorithm # Initiate the ProportionalAlgorithm
@@ -1342,7 +1342,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
self, old_preset_mode: str | None = None self, old_preset_mode: str | None = None
): # pylint: disable=unused-argument ): # pylint: disable=unused-argument
"""Reset to now the last change time""" """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) _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): 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 and old_preset_mode not in HIDDEN_PRESETS
): ):
self._last_temperature_measure = self._last_ext_temperature_measure = ( 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): 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""" """Extract the last_changed state from State or return now if not available"""
return ( return (
state.last_changed.astimezone(self._current_tz) state.last_changed.astimezone(self._current_tz)
if state.last_changed is not None if isinstance(state.last_changed, datetime)
else datetime.now(tz=self._current_tz) else self.now
) )
def get_last_updated_date_or_now(self, state: State) -> datetime: def get_last_updated_date_or_now(self, state: State) -> datetime:
"""Extract the last_changed state from State or return now if not available""" """Extract the last_changed state from State or return now if not available"""
return ( return (
state.last_updated.astimezone(self._current_tz) state.last_updated.astimezone(self._current_tz)
if state.last_updated is not None if isinstance(state.last_updated, datetime)
else datetime.now(tz=self._current_tz) else self.now
) )
@callback @callback
@@ -2004,7 +2004,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
if in_cycle: if in_cycle:
slope = self._window_auto_algo.check_age_last_measurement( slope = self._window_auto_algo.check_age_last_measurement(
temperature=self._ema_temp, temperature=self._ema_temp,
datetime_now=datetime.now(get_tz(self._hass)), datetime_now=self.now,
) )
else: else:
slope = self._window_auto_algo.add_temp_measurement( slope = self._window_auto_algo.add_temp_measurement(
@@ -2296,6 +2296,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
async def check_safety(self) -> bool: async def check_safety(self) -> bool:
"""Check if last temperature date is too long""" """Check if last temperature date is too long"""
now = self.now now = self.now
delta_temp = ( delta_temp = (
now - self._last_temperature_measure.replace(tzinfo=self._current_tz) 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, "device_power": self._device_power,
ATTR_MEAN_POWER_CYCLE: self.mean_cycle_power, ATTR_MEAN_POWER_CYCLE: self.mean_cycle_power,
ATTR_TOTAL_ENERGY: self.total_energy, ATTR_TOTAL_ENERGY: self.total_energy,
"last_update_datetime": datetime.now() "last_update_datetime": self.now.isoformat(),
.astimezone(self._current_tz)
.isoformat(),
"timezone": str(self._current_tz), "timezone": str(self._current_tz),
"temperature_unit": self.temperature_unit, "temperature_unit": self.temperature_unit,
"is_device_active": self.is_device_active, "is_device_active": self.is_device_active,
@@ -22,28 +22,12 @@ from homeassistant.const import (
STATE_NOT_HOME, STATE_NOT_HOME,
) )
from .const import ( from .const import * # pylint: disable=wildcard-import,unused-wildcard-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 .thermostat_switch import ThermostatOverSwitch from .thermostat_switch import ThermostatOverSwitch
from .thermostat_climate import ThermostatOverClimate from .thermostat_climate import ThermostatOverClimate
from .thermostat_valve import ThermostatOverValve from .thermostat_valve import ThermostatOverValve
from .thermostat_sonoff_trvzb import ThermostatOverSonoffTRVZB from .thermostat_climate_valve import ThermostatOverClimateValve
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -62,7 +46,9 @@ async def async_setup_entry(
unique_id = entry.entry_id unique_id = entry.entry_id
name = entry.data.get(CONF_NAME) name = entry.data.get(CONF_NAME)
vt_type = entry.data.get(CONF_THERMOSTAT_TYPE) 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: if vt_type == CONF_THERMOSTAT_CENTRAL_CONFIG:
return return
@@ -72,8 +58,8 @@ async def async_setup_entry(
if vt_type == CONF_THERMOSTAT_SWITCH: if vt_type == CONF_THERMOSTAT_SWITCH:
entity = ThermostatOverSwitch(hass, unique_id, name, entry.data) entity = ThermostatOverSwitch(hass, unique_id, name, entry.data)
elif vt_type == CONF_THERMOSTAT_CLIMATE: elif vt_type == CONF_THERMOSTAT_CLIMATE:
if is_sonoff_trvzb is True: if have_valve_regulation is True:
entity = ThermostatOverSonoffTRVZB(hass, unique_id, name, entry.data) entity = ThermostatOverClimateValve(hass, unique_id, name, entry.data)
else: else:
entity = ThermostatOverClimate(hass, unique_id, name, entry.data) entity = ThermostatOverClimate(hass, unique_id, name, entry.data)
elif vt_type == CONF_THERMOSTAT_VALVE: elif vt_type == CONF_THERMOSTAT_VALVE:
@@ -29,27 +29,6 @@ COMES_FROM = "comes_from"
_LOGGER = logging.getLogger(__name__) _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( def add_suggested_values_to_schema(
data_schema: vol.Schema, suggested_values: Mapping[str, Any] data_schema: vol.Schema, suggested_values: Mapping[str, Any]
) -> vol.Schema: ) -> vol.Schema:
@@ -162,17 +141,18 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
if COMES_FROM in self._infos: if COMES_FROM in self._infos:
del self._infos[COMES_FROM] del self._infos[COMES_FROM]
def check_sonoff_trvzb_nb_entities(self, data: dict) -> bool: def is_valve_regulation_selected(self, infos) -> bool:
"""Check the number of entities for Sonoff TRVZB""" """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 ret = True
if ( if self.is_valve_regulation_selected(self._infos):
self._infos.get(CONF_SONOFF_TRZB_MODE)
and data.get(CONF_OFFSET_CALIBRATION_LIST) is not None
):
nb_unders = len(self._infos.get(CONF_UNDERLYING_LIST)) nb_unders = len(self._infos.get(CONF_UNDERLYING_LIST))
nb_offset = len(data.get(CONF_OFFSET_CALIBRATION_LIST)) nb_offset = len(data.get(CONF_OFFSET_CALIBRATION_LIST, []))
nb_opening = len(data.get(CONF_OPENING_DEGREE_LIST)) nb_opening = len(data.get(CONF_OPENING_DEGREE_LIST, []))
nb_closing = len(data.get(CONF_CLOSING_DEGREE_LIST)) nb_closing = len(data.get(CONF_CLOSING_DEGREE_LIST, []))
if ( if (
nb_unders != nb_offset nb_unders != nb_offset
or nb_unders != nb_opening or nb_unders != nb_opening
@@ -181,7 +161,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
ret = False ret = False
return ret 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. """Validate the user input allows us to connect.
Data has the keys from STEP_*_DATA_SCHEMA with values provided by the user. 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 # Check that the number of offet_calibration and opening_degree and closing_degree are equals
# to the number of underlying entities # to the number of underlying entities
if not self.check_sonoff_trvzb_nb_entities(data): if not self.check_valve_regulation_nb_entities(data):
raise SonoffTRVZBNbEntitiesIncorrect() raise ValveRegulationNbEntitiesIncorrect()
def check_config_complete(self, infos) -> bool: def check_config_complete(self, infos) -> bool:
"""True if the config is now complete (ie all mandatory attributes are set)""" """True if the config is now complete (ie all mandatory attributes are set)"""
@@ -357,7 +337,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
): ):
return False return False
if not self.check_sonoff_trvzb_nb_entities(infos): if not self.check_valve_regulation_nb_entities(infos):
return False return False
return True return True
@@ -400,8 +380,8 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
errors[str(err)] = "service_configuration_format" errors[str(err)] = "service_configuration_format"
except ConfigurationNotCompleteError as err: except ConfigurationNotCompleteError as err:
errors["base"] = "configuration_not_complete" errors["base"] = "configuration_not_complete"
except SonoffTRVZBNbEntitiesIncorrect as err: except ValveRegulationNbEntitiesIncorrect as err:
errors["base"] = "sonoff_trvzb_nb_entities_incorrect" errors["base"] = "valve_regulation_nb_entities_incorrect"
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception") _LOGGER.exception("Unexpected exception")
errors["base"] = "unknown" errors["base"] = "unknown"
@@ -488,8 +468,8 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
]: ]:
menu_options.append("auto_start_stop") menu_options.append("auto_start_stop")
if self._infos.get(CONF_SONOFF_TRZB_MODE) is True: if self.is_valve_regulation_selected(self._infos):
menu_options.append("sonoff_trvzb") menu_options.append("valve_regulation")
menu_options.append("advanced") menu_options.append("advanced")
@@ -563,7 +543,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
if ( if (
self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_CLIMATE self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_CLIMATE
and user_input is not None 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 # Remove TPI info
for key in [ for key in [
@@ -621,19 +601,21 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
return await self.generic_step("auto_start_stop", schema, user_input, next_step) 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 self, user_input: dict | None = None
) -> FlowResult: ) -> FlowResult:
"""Handle the Sonoff TRVZB configuration step""" """Handle the valve regulation configuration step"""
_LOGGER.debug( _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 self._infos[COMES_FROM] = None
next_step = self.async_step_menu 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: async def async_step_tpi(self, user_input: dict | None = None) -> FlowResult:
"""Handle the TPI flow steps""" """Handle the TPI flow steps"""
@@ -141,7 +141,6 @@ STEP_THERMOSTAT_CLIMATE = vol.Schema( # pylint: disable=invalid-name
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN, multiple=True), selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN, multiple=True),
), ),
vol.Optional(CONF_AC_MODE, default=False): cv.boolean, vol.Optional(CONF_AC_MODE, default=False): cv.boolean,
vol.Optional(CONF_SONOFF_TRZB_MODE, default=False): cv.boolean,
vol.Optional( vol.Optional(
CONF_AUTO_REGULATION_MODE, default=CONF_AUTO_REGULATION_NONE CONF_AUTO_REGULATION_MODE, default=CONF_AUTO_REGULATION_NONE
): selector.SelectSelector( ): 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( vol.Required(CONF_OPENING_DEGREE_LIST): selector.EntitySelector(
selector.EntitySelectorConfig( selector.EntitySelectorConfig(
domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], multiple=True 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( selector.EntitySelectorConfig(
domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], multiple=True domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], multiple=True
), ),
@@ -526,8 +526,8 @@ class ConfigurationNotCompleteError(HomeAssistantError):
"""Error the configuration is not complete""" """Error the configuration is not complete"""
class SonoffTRVZBNbEntitiesIncorrect(HomeAssistantError): class ValveRegulationNbEntitiesIncorrect(HomeAssistantError):
"""Error to indicate there is an error in the configuration of the Sonoff TRVZB. """Error to indicate there is an error in the configuration of the TRV with valve regulation.
The number of specific entities is incorrect.""" The number of specific entities is incorrect."""
@@ -49,7 +49,8 @@ from .const import (
CONF_THERMOSTAT_TYPE, CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_CENTRAL_CONFIG, CONF_THERMOSTAT_CENTRAL_CONFIG,
CONF_USE_CENTRAL_BOILER_FEATURE, CONF_USE_CENTRAL_BOILER_FEATURE,
CONF_SONOFF_TRZB_MODE, CONF_AUTO_REGULATION_VALVE,
CONF_AUTO_REGULATION_MODE,
overrides, overrides,
) )
@@ -71,7 +72,9 @@ async def async_setup_entry(
unique_id = entry.entry_id unique_id = entry.entry_id
name = entry.data.get(CONF_NAME) name = entry.data.get(CONF_NAME)
vt_type = entry.data.get(CONF_THERMOSTAT_TYPE) 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 entities = None
@@ -102,13 +105,13 @@ async def async_setup_entry(
if ( if (
entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_VALVE 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)) entities.append(ValveOpenPercentSensor(hass, unique_id, name, entry.data))
if ( if (
entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CLIMATE entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CLIMATE
and not is_sonoff_trvzb and not have_valve_regulation
): ):
entities.append( entities.append(
RegulatedTemperatureSensor(hass, unique_id, name, entry.data) RegulatedTemperatureSensor(hass, unique_id, name, entry.data)
@@ -28,7 +28,7 @@
"presence": "Presence detection", "presence": "Presence detection",
"advanced": "Advanced parameters", "advanced": "Advanced parameters",
"auto_start_stop": "Auto start and stop", "auto_start_stop": "Auto start and stop",
"sonoff_trvzb": "Sonoff TRVZB configuration", "valve_regulation": "Valve regulation configuration",
"finalize": "All done", "finalize": "All done",
"configuration_not_complete": "Configuration not complete" "configuration_not_complete": "Configuration not complete"
} }
@@ -65,7 +65,7 @@
"use_motion_feature": "Use motion detection", "use_motion_feature": "Use motion detection",
"use_power_feature": "Use power management", "use_power_feature": "Use power management",
"use_presence_feature": "Use presence detection", "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" "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", "heater_keep_alive": "Switch keep-alive interval in seconds",
"proportional_function": "Algorithm", "proportional_function": "Algorithm",
"ac_mode": "AC mode", "ac_mode": "AC mode",
"sonoff_trvzb_mode": "SONOFF TRVZB mode",
"auto_regulation_mode": "Self-regulation", "auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold", "auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimum period", "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.", "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)", "proportional_function": "Algorithm to use (TPI is the only one for now)",
"ac_mode": "Use the Air Conditioning (AC) mode", "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_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_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", "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]" "central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]"
} }
}, },
"sonoff_trvzb": { "valve_regulation": {
"title": "Sonoff TRVZB configuration", "title": "Self-regulation with valve",
"description": "Specific Sonoff TRVZB configuration", "description": "Configuration for self-regulation with direct control of the valve",
"data": { "data": {
"offset_calibration_entity_ids": "Offset calibration entities", "offset_calibration_entity_ids": "Offset calibration entities",
"opening_degree_entity_ids": "Opening degree entities", "opening_degree_entity_ids": "Opening degree entities",
@@ -229,9 +227,9 @@
"proportional_function": "Algorithm" "proportional_function": "Algorithm"
}, },
"data_description": { "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", "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)" "proportional_function": "Algorithm to use (TPI is the only one for now)"
} }
} }
@@ -274,7 +272,7 @@
"presence": "Presence detection", "presence": "Presence detection",
"advanced": "Advanced parameters", "advanced": "Advanced parameters",
"auto_start_stop": "Auto start and stop", "auto_start_stop": "Auto start and stop",
"sonoff_trvzb": "Sonoff TRVZB configuration", "valve_regulation": "Valve regulation configuration",
"finalize": "All done", "finalize": "All done",
"configuration_not_complete": "Configuration not complete" "configuration_not_complete": "Configuration not complete"
} }
@@ -311,7 +309,7 @@
"use_motion_feature": "Use motion detection", "use_motion_feature": "Use motion detection",
"use_power_feature": "Use power management", "use_power_feature": "Use power management",
"use_presence_feature": "Use presence detection", "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" "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", "heater_keep_alive": "Switch keep-alive interval in seconds",
"proportional_function": "Algorithm", "proportional_function": "Algorithm",
"ac_mode": "AC mode", "ac_mode": "AC mode",
"sonoff_trvzb_mode": "SONOFF TRVZB mode",
"auto_regulation_mode": "Self-regulation", "auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold", "auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimum period", "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.", "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)", "proportional_function": "Algorithm to use (TPI is the only one for now)",
"ac_mode": "Use the Air Conditioning (AC) mode", "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_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_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", "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]" "central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]"
} }
}, },
"sonoff_trvzb": { "valve_regulation": {
"title": "Sonoff TRVZB configuration - {name}", "title": "Self-regulation with valve - {name}",
"description": "Specific Sonoff TRVZB configuration", "description": "Configuration for self-regulation with direct control of the valve",
"data": { "data": {
"offset_calibration_entity_ids": "Offset calibration entities", "offset_calibration_entity_ids": "Offset calibration entities",
"opening_degree_entity_ids": "Opening degree entities", "opening_degree_entity_ids": "Opening degree entities",
@@ -475,9 +471,9 @@
"proportional_function": "Algorithm" "proportional_function": "Algorithm"
}, },
"data_description": { "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", "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)" "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", "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.", "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", "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": { "abort": {
"already_configured": "Device is already configured" "already_configured": "Device is already configured"
@@ -510,7 +506,8 @@
"auto_regulation_medium": "Medium", "auto_regulation_medium": "Medium",
"auto_regulation_light": "Light", "auto_regulation_light": "Light",
"auto_regulation_expert": "Expert", "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": { "auto_fan_mode": {
@@ -1258,6 +1258,13 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
self.choose_auto_regulation_mode(CONF_AUTO_REGULATION_SLOW) self.choose_auto_regulation_mode(CONF_AUTO_REGULATION_SLOW)
elif auto_regulation_mode == "Expert": elif auto_regulation_mode == "Expert":
self.choose_auto_regulation_mode(CONF_AUTO_REGULATION_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() await self._send_regulated_temperature()
self.update_custom_attributes() self.update_custom_attributes()
@@ -1,5 +1,5 @@
# pylint: disable=line-too-long, too-many-lines, abstract-method # 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 import logging
from datetime import datetime from datetime import datetime
@@ -7,7 +7,7 @@ from datetime import datetime
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.components.climate import HVACMode, HVACAction from homeassistant.components.climate import HVACMode, HVACAction
from .underlyings import UnderlyingSonoffTRVZB from .underlyings import UnderlyingValveRegulation
# from .commons import NowClass, round_to_nearest # from .commons import NowClass, round_to_nearest
from .base_thermostat import ConfigData from .base_thermostat import ConfigData
@@ -21,14 +21,14 @@ from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
class ThermostatOverSonoffTRVZB(ThermostatOverClimate): class ThermostatOverClimateValve(ThermostatOverClimate):
"""This class represent a VTherm over a Sonoff TRVZB climate""" """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 _entity_component_unrecorded_attributes = ThermostatOverClimate._entity_component_unrecorded_attributes.union( # pylint: disable=protected-access
frozenset( frozenset(
{ {
"is_over_climate", "is_over_climate",
"is_over_sonoff_trvzb", "have_valve_regulation",
"underlying_entities", "underlying_entities",
"on_time_sec", "on_time_sec",
"off_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 _valve_open_percent: int | None = None
_last_calculation_timestamp: datetime | None = None _last_calculation_timestamp: datetime | None = None
_auto_regulation_dpercent: float | None = None _auto_regulation_dpercent: float | None = None
@@ -49,8 +49,8 @@ class ThermostatOverSonoffTRVZB(ThermostatOverClimate):
def __init__( def __init__(
self, hass: HomeAssistant, unique_id: str, name: str, entry_infos: ConfigData self, hass: HomeAssistant, unique_id: str, name: str, entry_infos: ConfigData
): ):
"""Initialize the ThermostatOverSonoffTRVZB class""" """Initialize the ThermostatOverClimateValve class"""
_LOGGER.debug("%s - creating a ThermostatOverSonoffTRVZB VTherm", name) _LOGGER.debug("%s - creating a ThermostatOverClimateValve VTherm", name)
super().__init__(hass, unique_id, name, entry_infos) super().__init__(hass, unique_id, name, entry_infos)
# self._valve_open_percent: int = 0 # self._valve_open_percent: int = 0
# self._last_calculation_timestamp: datetime | None = None # self._last_calculation_timestamp: datetime | None = None
@@ -60,8 +60,8 @@ class ThermostatOverSonoffTRVZB(ThermostatOverClimate):
@overrides @overrides
def post_init(self, config_entry: ConfigData): def post_init(self, config_entry: ConfigData):
"""Initialize the Thermostat and underlyings """Initialize the Thermostat and underlyings
Beware that the underlyings list contains the climate which represent the Sonoff TRVZB Beware that the underlyings list contains the climate which represent the TRV
but also the UnderlyingSonoff which reprensent the valve""" but also the UnderlyingValveRegulation which reprensent the valve"""
super().post_init(config_entry) super().post_init(config_entry)
@@ -90,7 +90,7 @@ class ThermostatOverSonoffTRVZB(ThermostatOverClimate):
offset = config_entry.get(CONF_OFFSET_CALIBRATION_LIST)[idx] offset = config_entry.get(CONF_OFFSET_CALIBRATION_LIST)[idx]
opening = config_entry.get(CONF_OPENING_DEGREE_LIST)[idx] opening = config_entry.get(CONF_OPENING_DEGREE_LIST)[idx]
closing = config_entry.get(CONF_CLOSING_DEGREE_LIST)[idx] closing = config_entry.get(CONF_CLOSING_DEGREE_LIST)[idx]
under = UnderlyingSonoffTRVZB( under = UnderlyingValveRegulation(
hass=self._hass, hass=self._hass,
thermostat=self, thermostat=self,
offset_calibration_entity_id=offset, offset_calibration_entity_id=offset,
@@ -98,19 +98,19 @@ class ThermostatOverSonoffTRVZB(ThermostatOverClimate):
closing_degree_entity_id=closing, closing_degree_entity_id=closing,
climate_underlying=self._underlyings[idx], climate_underlying=self._underlyings[idx],
) )
self._underlyings_sonoff_trvzb.append(under) self._underlyings_valve_regulation.append(under)
@overrides @overrides
def update_custom_attributes(self): def update_custom_attributes(self):
"""Custom attributes""" """Custom attributes"""
super().update_custom_attributes() super().update_custom_attributes()
self._attr_extra_state_attributes["is_over_sonoff_trvzb"] = ( self._attr_extra_state_attributes["have_valve_regulation"] = (
self.is_over_sonoff_trvzb self.have_valve_regulation
) )
self._attr_extra_state_attributes["underlying_sonoff_trvzb_entities"] = [ self._attr_extra_state_attributes["underlyings_valve_regulation"] = [
underlying.entity_id for underlying in self._underlyings_sonoff_trvzb underlying.entity_id for underlying in self._underlyings_valve_regulation
] ]
self._attr_extra_state_attributes["on_percent"] = ( self._attr_extra_state_attributes["on_percent"] = (
@@ -233,12 +233,12 @@ class ThermostatOverSonoffTRVZB(ThermostatOverClimate):
self._attr_min_temp, self._attr_min_temp,
) )
for under in self._underlyings_sonoff_trvzb: for under in self._underlyings_valve_regulation:
await under.set_valve_open_percent() await under.set_valve_open_percent()
@property @property
def is_over_sonoff_trvzb(self) -> bool: def have_valve_regulation(self) -> bool:
"""True if the Thermostat is over_sonoff_trvzb""" """True if the Thermostat is regulated by valve"""
return True return True
@property @property
@@ -264,11 +264,16 @@ class ThermostatOverSonoffTRVZB(ThermostatOverClimate):
@property @property
def hvac_action(self) -> HVACAction | None: 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 @property
def is_device_active(self) -> bool: def is_device_active(self) -> bool:
"""A hack to overrides the state from underlyings""" """A hack to overrides the state from underlyings"""
return self.valve_open_percent > 0 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
@@ -28,7 +28,7 @@
"presence": "Presence detection", "presence": "Presence detection",
"advanced": "Advanced parameters", "advanced": "Advanced parameters",
"auto_start_stop": "Auto start and stop", "auto_start_stop": "Auto start and stop",
"sonoff_trvzb": "Sonoff TRVZB configuration", "valve_regulation": "Valve regulation configuration",
"finalize": "All done", "finalize": "All done",
"configuration_not_complete": "Configuration not complete" "configuration_not_complete": "Configuration not complete"
} }
@@ -77,7 +77,6 @@
"heater_keep_alive": "Switch keep-alive interval in seconds", "heater_keep_alive": "Switch keep-alive interval in seconds",
"proportional_function": "Algorithm", "proportional_function": "Algorithm",
"ac_mode": "AC mode", "ac_mode": "AC mode",
"sonoff_trvzb_mode": "SONOFF TRVZB mode",
"auto_regulation_mode": "Self-regulation", "auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold", "auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimum period", "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.", "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)", "proportional_function": "Algorithm to use (TPI is the only one for now)",
"ac_mode": "Use the Air Conditioning (AC) mode", "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_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_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", "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]" "central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]"
} }
}, },
"sonoff_trvzb": { "valve_regulation": {
"title": "Sonoff TRVZB configuration", "title": "Self-regulation with valve",
"description": "Specific Sonoff TRVZB configuration", "description": "Configuration for self-regulation with direct control of the valve",
"data": { "data": {
"offset_calibration_entity_ids": "Offset calibration entities", "offset_calibration_entity_ids": "Offset calibration entities",
"opening_degree_entity_ids": "Opening degree entities", "opening_degree_entity_ids": "Opening degree entities",
@@ -229,9 +227,9 @@
"proportional_function": "Algorithm" "proportional_function": "Algorithm"
}, },
"data_description": { "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", "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)" "proportional_function": "Algorithm to use (TPI is the only one for now)"
} }
} }
@@ -274,7 +272,7 @@
"presence": "Presence detection", "presence": "Presence detection",
"advanced": "Advanced parameters", "advanced": "Advanced parameters",
"auto_start_stop": "Auto start and stop", "auto_start_stop": "Auto start and stop",
"sonoff_trvzb": "Sonoff TRVZB configuration", "valve_regulation": "Valve regulation configuration",
"finalize": "All done", "finalize": "All done",
"configuration_not_complete": "Configuration not complete" "configuration_not_complete": "Configuration not complete"
} }
@@ -311,7 +309,7 @@
"use_motion_feature": "Use motion detection", "use_motion_feature": "Use motion detection",
"use_power_feature": "Use power management", "use_power_feature": "Use power management",
"use_presence_feature": "Use presence detection", "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" "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", "heater_keep_alive": "Switch keep-alive interval in seconds",
"proportional_function": "Algorithm", "proportional_function": "Algorithm",
"ac_mode": "AC mode", "ac_mode": "AC mode",
"sonoff_trvzb_mode": "SONOFF TRVZB mode",
"auto_regulation_mode": "Self-regulation", "auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold", "auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimum period", "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.", "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)", "proportional_function": "Algorithm to use (TPI is the only one for now)",
"ac_mode": "Use the Air Conditioning (AC) mode", "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_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_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", "auto_regulation_periode_min": "Duration in minutes between two regulation update",
@@ -454,7 +450,7 @@
} }
}, },
"central_boiler": { "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`", "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": { "data": {
"central_boiler_activation_service": "Command to turn-on", "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]" "central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]"
} }
}, },
"sonoff_trvzb": { "valve_regulation": {
"title": "Sonoff TRVZB configuration", "title": "Self-regulation with valve - {name}",
"description": "Specific Sonoff TRVZB configuration", "description": "Configuration for self-regulation with direct control of the valve",
"data": { "data": {
"offset_calibration_entity_ids": "Offset calibration entities", "offset_calibration_entity_ids": "Offset calibration entities",
"opening_degree_entity_ids": "Opening degree entities", "opening_degree_entity_ids": "Opening degree entities",
@@ -475,9 +471,9 @@
"proportional_function": "Algorithm" "proportional_function": "Algorithm"
}, },
"data_description": { "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", "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)" "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", "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.", "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", "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": { "abort": {
"already_configured": "Device is already configured" "already_configured": "Device is already configured"
@@ -510,7 +506,8 @@
"auto_regulation_medium": "Medium", "auto_regulation_medium": "Medium",
"auto_regulation_light": "Light", "auto_regulation_light": "Light",
"auto_regulation_expert": "Expert", "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": { "auto_fan_mode": {
@@ -28,7 +28,7 @@
"presence": "Détection de présence", "presence": "Détection de présence",
"advanced": "Paramètres avancés", "advanced": "Paramètres avancés",
"auto_start_stop": "Allumage/extinction automatique", "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", "finalize": "Finaliser la création",
"configuration_not_complete": "Configuration incomplète" "configuration_not_complete": "Configuration incomplète"
} }
@@ -77,7 +77,6 @@
"heater_keep_alive": "keep-alive (sec)", "heater_keep_alive": "keep-alive (sec)",
"proportional_function": "Algorithme", "proportional_function": "Algorithme",
"ac_mode": "AC mode ?", "ac_mode": "AC mode ?",
"sonoff_trvzb_mode": "Mode Sonoff TRVZB",
"auto_regulation_mode": "Auto-régulation", "auto_regulation_mode": "Auto-régulation",
"auto_regulation_dtemp": "Seuil de régulation", "auto_regulation_dtemp": "Seuil de régulation",
"auto_regulation_periode_min": "Période minimale 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.", "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)", "proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)",
"ac_mode": "Utilisation du mode Air Conditionné (AC)", "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": "Utilisation de l'auto-régulation faite par VTherm",
"auto_regulation_mode": "Ajustement automatique de la température cible", "auto_regulation_dtemp": "Le seuil en ° (ou % pour les vannes) en-dessous duquel la régulation ne sera pas envoyée",
"auto_regulation_dtemp": "Le seuil en ° (ou % pour les valves) 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_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", "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", "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]" "central_boiler_deactivation_service": "Commande à éxecuter pour étiendre la chaudière centrale au format entity_id/service_name[/attribut:valeur]"
} }
}, },
"sonoff_trvzb": { "valve_regulation": {
"title": "Configuration Sonoff TRVZB", "title": "Auto-régulation par vanne - {name}",
"description": "Configuration spécifique des Sonoff TRVZB", "description": "Configuration de l'auto-régulation par controle direct de la vanne",
"data": { "data": {
"offset_calibration_entity_ids": "Entités de 'Offset calibration'", "offset_calibration_entity_ids": "Entités de 'calibrage du décalage''",
"opening_degree_entity_ids": "Entités de 'Opening degree'", "opening_degree_entity_ids": "Entités 'ouverture de vanne'",
"closing_degree_entity_ids": "Entités de 'Closing degree'", "closing_degree_entity_ids": "Entités 'fermeture de la vanne'",
"proportional_function": "Algorithme" "proportional_function": "Algorithme"
}, },
"data_description": { "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", "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 'opening degree' entities. 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 'closing degree' entities. 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)" "proportional_function": "Algorithme à utiliser (seulement TPI est disponible)"
} }
} }
@@ -274,7 +272,7 @@
"presence": "Détection de présence", "presence": "Détection de présence",
"advanced": "Paramètres avancés", "advanced": "Paramètres avancés",
"auto_start_stop": "Allumage/extinction automatique", "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", "finalize": "Finaliser les modifications",
"configuration_not_complete": "Configuration incomplète" "configuration_not_complete": "Configuration incomplète"
} }
@@ -323,7 +321,6 @@
"heater_keep_alive": "keep-alive (sec)", "heater_keep_alive": "keep-alive (sec)",
"proportional_function": "Algorithme", "proportional_function": "Algorithme",
"ac_mode": "AC mode ?", "ac_mode": "AC mode ?",
"sonoff_trvzb_mode": "Mode Sonoff TRVZB",
"auto_regulation_mode": "Auto-régulation", "auto_regulation_mode": "Auto-régulation",
"auto_regulation_dtemp": "Seuil de régulation", "auto_regulation_dtemp": "Seuil de régulation",
"auto_regulation_periode_min": "Période minimale 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.", "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)", "proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)",
"ac_mode": "Utilisation du mode Air Conditionné (AC)", "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": "Utilisation de l'auto-régulation faite par VTherm",
"auto_regulation_mode": "Ajustement automatique de la température cible", "auto_regulation_dtemp": "Le seuil en ° (ou % pour les vannes) en-dessous duquel la régulation ne sera pas envoyée",
"auto_regulation_dtemp": "Le seuil en ° (ou % pour les valves) 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_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", "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", "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]" "central_boiler_deactivation_service": "Commande à éxecuter pour étiendre la chaudière centrale au format entity_id/service_name[/attribut:valeur]"
} }
}, },
"sonoff_trvzb": { "valve_regulation": {
"title": "Configuration Sonoff TRVZB - {name}", "title": "Auto-régulation par vanne - {name}",
"description": "Configuration spécifique des Sonoff TRVZB", "description": "Configuration de l'auto-régulation par controle direct de la vanne",
"data": { "data": {
"offset_calibration_entity_ids": "Entités de 'Offset calibration'", "offset_calibration_entity_ids": "Entités de 'calibrage du décalage''",
"opening_degree_entity_ids": "Entités de 'Opening degree'", "opening_degree_entity_ids": "Entités 'ouverture de vanne'",
"closing_degree_entity_ids": "Entités de 'Closing degree'", "closing_degree_entity_ids": "Entités 'fermeture de la vanne'",
"proportional_function": "Algorithme" "proportional_function": "Algorithme"
}, },
"data_description": { "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", "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 'opening degree' entities. 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 'closing degree' entities. 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)" "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.", "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.", "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", "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": { "abort": {
"already_configured": "Le device est déjà configuré" "already_configured": "Le device est déjà configuré"
@@ -504,7 +500,8 @@
"auto_regulation_medium": "Moyenne", "auto_regulation_medium": "Moyenne",
"auto_regulation_light": "Légère", "auto_regulation_light": "Légère",
"auto_regulation_expert": "Expert", "auto_regulation_expert": "Expert",
"auto_regulation_none": "Aucune" "auto_regulation_none": "Aucune",
"auto_regulation_valve": "Contrôle direct de la vanne"
} }
}, },
"auto_fan_mode": { "auto_fan_mode": {
@@ -53,8 +53,8 @@ class UnderlyingEntityType(StrEnum):
# a valve # a valve
VALVE = "valve" VALVE = "valve"
# a Sonoff TRVZB # a direct valve regulation
SONOFF_TRVZB = "sonoff_trvzb" VALVE_REGULATION = "valve_regulation"
class UnderlyingEntity: class UnderlyingEntity:
@@ -871,7 +871,11 @@ class UnderlyingValve(UnderlyingEntity):
_last_sent_temperature = None _last_sent_temperature = None
def __init__( 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: ) -> None:
"""Initialize the underlying valve""" """Initialize the underlying valve"""
@@ -1014,8 +1018,8 @@ class UnderlyingValve(UnderlyingEntity):
self._cancel_cycle() self._cancel_cycle()
class UnderlyingSonoffTRVZB(UnderlyingValve): class UnderlyingValveRegulation(UnderlyingValve):
"""A specific underlying class for Sonoff TRVZB TRV""" """A specific underlying class for Valve regulation"""
_offset_calibration_entity_id: str _offset_calibration_entity_id: str
_opening_degree_entity_id: str _opening_degree_entity_id: str
@@ -1030,8 +1034,13 @@ class UnderlyingSonoffTRVZB(UnderlyingValve):
closing_degree_entity_id: str, closing_degree_entity_id: str,
climate_underlying: UnderlyingClimate, climate_underlying: UnderlyingClimate,
) -> None: ) -> None:
"""Initialize the underlying Sonoff TRV""" """Initialize the underlying TRV with valve regulation"""
super().__init__(hass, thermostat, opening_degree_entity_id) super().__init__(
hass,
thermostat,
opening_degree_entity_id,
type=UnderlyingEntityType.VALVE_REGULATION,
)
self._offset_calibration_entity_id = offset_calibration_entity_id self._offset_calibration_entity_id = offset_calibration_entity_id
self._opening_degree_entity_id = opening_degree_entity_id self._opening_degree_entity_id = opening_degree_entity_id
self._closing_degree_entity_id = closing_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._max_opening_degree = self._hass.states.get(
self._opening_degree_entity_id self._opening_degree_entity_id
).attributes.get("max") ).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 = ( if self.have_offset_calibration_entity:
self._max_opening_degree is not None self._min_offset_calibration = self._hass.states.get(
and self._min_offset_calibration is not None self._offset_calibration_entity_id
and self._max_offset_calibration is not None ).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: if not self._is_min_max_initialized:
@@ -1074,7 +1087,7 @@ class UnderlyingSonoffTRVZB(UnderlyingValve):
# Send closing_degree if set # Send closing_degree if set
closing_degree = None closing_degree = None
if self._closing_degree_entity_id is not None: if self.have_closing_degree_entity:
await self._send_value_to_number( await self._send_value_to_number(
self._closing_degree_entity_id, self._closing_degree_entity_id,
closing_degree := self._max_opening_degree - self._percent_open, 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 # send offset_calibration to the difference between target temp and local temp
offset = None offset = None
if self._offset_calibration_entity_id is not None: if self.have_offset_calibration_entity:
if ( if (
(local_temp := self._climate_underlying.underlying_current_temperature) (local_temp := self._climate_underlying.underlying_current_temperature)
is not None is not None
@@ -1107,7 +1120,7 @@ class UnderlyingSonoffTRVZB(UnderlyingValve):
) )
_LOGGER.debug( _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, self,
offset, offset,
self._percent_open, self._percent_open,
@@ -1129,6 +1142,16 @@ class UnderlyingSonoffTRVZB(UnderlyingValve):
"""The offset_calibration_entity_id""" """The offset_calibration_entity_id"""
return self._closing_degree_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 @property
def hvac_modes(self) -> list[HVACMode]: def hvac_modes(self) -> list[HVACMode]:
"""Get the hvac_modes""" """Get the hvac_modes"""
+3 -4
View File
@@ -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 """ """ Test the Versatile Thermostat config flow """
from homeassistant import data_entry_flow
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.config_entries import SOURCE_USER, ConfigEntry 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_USE_ADVANCED_CENTRAL_CONFIG: False,
CONF_USED_BY_CENTRAL_BOILER: False, CONF_USED_BY_CENTRAL_BOILER: False,
CONF_USE_CENTRAL_MODE: 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"]
assert result["result"].domain == DOMAIN 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_USED_BY_CENTRAL_BOILER: False,
CONF_USE_AUTO_START_STOP_FEATURE: True, CONF_USE_AUTO_START_STOP_FEATURE: True,
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_MEDIUM, 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"]
assert result["result"].domain == DOMAIN assert result["result"].domain == DOMAIN