diff --git a/custom_components/versatile_thermostat/config_schema.py b/custom_components/versatile_thermostat/config_schema.py index 5c9ece7..bcb21cf 100644 --- a/custom_components/versatile_thermostat/config_schema.py +++ b/custom_components/versatile_thermostat/config_schema.py @@ -257,12 +257,11 @@ STEP_WINDOW_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name STEP_CENTRAL_WINDOW_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name { vol.Optional(CONF_WINDOW_DELAY, default=30): cv.positive_int, + vol.Optional(CONF_WINDOW_OFF_DELAY, default=30): cv.positive_int, vol.Optional(CONF_WINDOW_AUTO_OPEN_THRESHOLD, default=3): vol.Coerce(float), vol.Optional(CONF_WINDOW_AUTO_CLOSE_THRESHOLD, default=0): vol.Coerce(float), vol.Optional(CONF_WINDOW_AUTO_MAX_DURATION, default=30): cv.positive_int, - vol.Optional( - CONF_WINDOW_ACTION, default=CONF_WINDOW_TURN_OFF - ): selector.SelectSelector( + vol.Optional(CONF_WINDOW_ACTION, default=CONF_WINDOW_TURN_OFF): selector.SelectSelector( selector.SelectSelectorConfig( options=CONF_WINDOW_ACTIONS, translation_key="window_action", @@ -275,9 +274,8 @@ STEP_CENTRAL_WINDOW_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name STEP_CENTRAL_WINDOW_WO_AUTO_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name { vol.Optional(CONF_WINDOW_DELAY, default=30): cv.positive_int, - vol.Optional( - CONF_WINDOW_ACTION, default=CONF_WINDOW_TURN_OFF - ): selector.SelectSelector( + vol.Optional(CONF_WINDOW_OFF_DELAY, default=30): cv.positive_int, + vol.Optional(CONF_WINDOW_ACTION, default=CONF_WINDOW_TURN_OFF): selector.SelectSelector( selector.SelectSelectorConfig( options=CONF_WINDOW_ACTIONS, translation_key="window_action", diff --git a/custom_components/versatile_thermostat/const.py b/custom_components/versatile_thermostat/const.py index be1fc46..745af24 100644 --- a/custom_components/versatile_thermostat/const.py +++ b/custom_components/versatile_thermostat/const.py @@ -73,6 +73,7 @@ CONF_DEVICE_POWER = "device_power" CONF_CYCLE_MIN = "cycle_min" CONF_PROP_FUNCTION = "proportional_function" CONF_WINDOW_DELAY = "window_delay" +CONF_WINDOW_OFF_DELAY = "window_off_delay" CONF_MOTION_DELAY = "motion_delay" CONF_MOTION_OFF_DELAY = "motion_off_delay" CONF_MOTION_PRESET = "motion_preset" @@ -270,6 +271,7 @@ ALL_CONF = ( CONF_MAX_POWER_SENSOR, CONF_WINDOW_SENSOR, CONF_WINDOW_DELAY, + CONF_WINDOW_OFF_DELAY, CONF_WINDOW_AUTO_OPEN_THRESHOLD, CONF_WINDOW_AUTO_CLOSE_THRESHOLD, CONF_WINDOW_AUTO_MAX_DURATION, diff --git a/custom_components/versatile_thermostat/feature_window_manager.py b/custom_components/versatile_thermostat/feature_window_manager.py index 2c4619f..24d809b 100644 --- a/custom_components/versatile_thermostat/feature_window_manager.py +++ b/custom_components/versatile_thermostat/feature_window_manager.py @@ -46,6 +46,7 @@ class FeatureWindowManager(BaseFeatureManager): "is_window_configured", "is_window_bypass", "window_delay_sec", + "window_off_delay_sec", "window_auto_configured", "window_auto_open_threshold", "window_auto_close_threshold", @@ -67,6 +68,7 @@ class FeatureWindowManager(BaseFeatureManager): self._is_window_bypass: bool = False self._window_action: str = None self._window_delay_sec: int | None = 0 + self._window_off_delay_sec: int | None = 0 self._is_configured: bool = False self._is_window_auto_configured: bool = False self._window_call_cancel: callable = None @@ -81,6 +83,8 @@ class FeatureWindowManager(BaseFeatureManager): self._window_sensor_entity_id = entry_infos.get(CONF_WINDOW_SENSOR) self._window_delay_sec = entry_infos.get(CONF_WINDOW_DELAY) + # default is the WINDOW_ON delay if not configured + self._window_off_delay_sec = entry_infos.get(CONF_WINDOW_OFF_DELAY, self._window_delay_sec) self._window_action = ( entry_infos.get(CONF_WINDOW_ACTION) or CONF_WINDOW_TURN_OFF @@ -191,7 +195,7 @@ class FeatureWindowManager(BaseFeatureManager): self._hass, self._window_sensor_entity_id, new_state.state, - timedelta(seconds=self._window_delay_sec), + timedelta(seconds=delay), ) except ConditionError: long_enough = False @@ -221,13 +225,12 @@ class FeatureWindowManager(BaseFeatureManager): self._vtherm.update_custom_attributes() + delay = self._window_delay_sec if new_state.state == STATE_ON else self._window_off_delay_sec if new_state is None or old_state is None or new_state.state == old_state.state: return try_window_condition self.dearm_window_timer() - self._window_call_cancel = async_call_later( - self.hass, timedelta(seconds=self._window_delay_sec), try_window_condition - ) + self._window_call_cancel = async_call_later(self.hass, timedelta(seconds=delay), try_window_condition) # For testing purpose we need to access the inner function return try_window_condition @@ -433,6 +436,7 @@ class FeatureWindowManager(BaseFeatureManager): "is_window_bypass": self._is_window_bypass, "window_sensor_entity_id": self._window_sensor_entity_id, "window_delay_sec": self._window_delay_sec, + "window_off_delay_sec": self._window_off_delay_sec, "is_window_configured": self._is_configured, "is_window_auto_configured": self._is_window_auto_configured, "window_auto_open_threshold": self._window_auto_open_threshold, @@ -512,9 +516,14 @@ class FeatureWindowManager(BaseFeatureManager): @property def window_delay_sec(self) -> bool: - """Return the motion delay""" + """Return the window on delay""" return self._window_delay_sec + @property + def window_off_delay_sec(self) -> bool: + """Return the window off delay""" + return self._window_off_delay_sec + @property def window_action(self) -> bool: """Return the window action""" diff --git a/custom_components/versatile_thermostat/strings.json b/custom_components/versatile_thermostat/strings.json index 491ce22..cec7dc2 100644 --- a/custom_components/versatile_thermostat/strings.json +++ b/custom_components/versatile_thermostat/strings.json @@ -123,7 +123,8 @@ "description": "Open window management.\nYou can also configure automatic window open detection based on temperature decrease", "data": { "window_sensor_entity_id": "Window sensor entity id", - "window_delay": "Window sensor delay (seconds)", + "window_delay": "Window sensor 'on' delay (seconds)", + "window_off_delay": "Window sensor 'off' delay (seconds)", "window_auto_open_threshold": "Temperature decrease threshold for automatic window open detection (in °/hours)", "window_auto_close_threshold": "Temperature increase threshold for end of automatic detection (in °/hours)", "window_auto_max_duration": "Maximum duration of automatic window open detection (in min)", @@ -132,7 +133,8 @@ }, "data_description": { "window_sensor_entity_id": "Leave empty if no window sensor should be used and to use the automatic detection", - "window_delay": "The delay in seconds before sensor detection is taken into account", + "window_delay": "The delay in seconds before sensor 'on' detection is taken into account", + "window_off_delay": "The delay in seconds before sensor 'off' detection is taken into account. Leave it empty to use the same value as window on delay", "window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used", "window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not used", "window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used", @@ -369,7 +371,8 @@ "description": "Open window management.\nYou can also configure automatic window open detection based on temperature decrease", "data": { "window_sensor_entity_id": "Window sensor entity id", - "window_delay": "Window sensor delay (seconds)", + "window_delay": "Window sensor 'on' delay (seconds)", + "window_off_delay": "Window sensor 'off' delay (seconds)", "window_auto_open_threshold": "Temperature decrease threshold for automatic window open detection (in °/hours)", "window_auto_close_threshold": "Temperature increase threshold for end of automatic detection (in °/hours)", "window_auto_max_duration": "Maximum duration of automatic window open detection (in min)", @@ -378,8 +381,8 @@ }, "data_description": { "window_sensor_entity_id": "Leave empty if no window sensor should be used and to use the automatic detection", - "window_delay": "The delay in seconds before sensor detection is taken into account", - "window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used", + "window_delay": "The delay in seconds before sensor 'on' detection is taken into account", + "window_off_delay": "The delay in seconds before sensor 'off' detection is taken into account. Leave it empty to use the same value as window on delay", "window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not used", "window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used", "use_window_central_config": "Check to use the central window configuration. Uncheck to use a specific window configuration for this VTherm", diff --git a/custom_components/versatile_thermostat/translations/en.json b/custom_components/versatile_thermostat/translations/en.json index 160c686..cec7dc2 100644 --- a/custom_components/versatile_thermostat/translations/en.json +++ b/custom_components/versatile_thermostat/translations/en.json @@ -123,7 +123,8 @@ "description": "Open window management.\nYou can also configure automatic window open detection based on temperature decrease", "data": { "window_sensor_entity_id": "Window sensor entity id", - "window_delay": "Window sensor delay (seconds)", + "window_delay": "Window sensor 'on' delay (seconds)", + "window_off_delay": "Window sensor 'off' delay (seconds)", "window_auto_open_threshold": "Temperature decrease threshold for automatic window open detection (in °/hours)", "window_auto_close_threshold": "Temperature increase threshold for end of automatic detection (in °/hours)", "window_auto_max_duration": "Maximum duration of automatic window open detection (in min)", @@ -132,7 +133,8 @@ }, "data_description": { "window_sensor_entity_id": "Leave empty if no window sensor should be used and to use the automatic detection", - "window_delay": "The delay in seconds before sensor detection is taken into account", + "window_delay": "The delay in seconds before sensor 'on' detection is taken into account", + "window_off_delay": "The delay in seconds before sensor 'off' detection is taken into account. Leave it empty to use the same value as window on delay", "window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used", "window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not used", "window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used", @@ -369,7 +371,8 @@ "description": "Open window management.\nYou can also configure automatic window open detection based on temperature decrease", "data": { "window_sensor_entity_id": "Window sensor entity id", - "window_delay": "Window sensor delay (seconds)", + "window_delay": "Window sensor 'on' delay (seconds)", + "window_off_delay": "Window sensor 'off' delay (seconds)", "window_auto_open_threshold": "Temperature decrease threshold for automatic window open detection (in °/hours)", "window_auto_close_threshold": "Temperature increase threshold for end of automatic detection (in °/hours)", "window_auto_max_duration": "Maximum duration of automatic window open detection (in min)", @@ -378,8 +381,8 @@ }, "data_description": { "window_sensor_entity_id": "Leave empty if no window sensor should be used and to use the automatic detection", - "window_delay": "The delay in seconds before sensor detection is taken into account", - "window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used", + "window_delay": "The delay in seconds before sensor 'on' detection is taken into account", + "window_off_delay": "The delay in seconds before sensor 'off' detection is taken into account. Leave it empty to use the same value as window on delay", "window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not used", "window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used", "use_window_central_config": "Check to use the central window configuration. Uncheck to use a specific window configuration for this VTherm", @@ -488,7 +491,8 @@ "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", - "valve_regulation_nb_entities_incorrect": "The number of valve entities for valve regulation 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", + "min_opening_degrees_format": "A comma separated list of positive integer is expected. Example: 20, 25, 30" }, "abort": { "already_configured": "Device is already configured" diff --git a/custom_components/versatile_thermostat/translations/fr.json b/custom_components/versatile_thermostat/translations/fr.json index c3c2cfe..bbeb9c6 100644 --- a/custom_components/versatile_thermostat/translations/fr.json +++ b/custom_components/versatile_thermostat/translations/fr.json @@ -123,7 +123,8 @@ "description": "Coupe le radiateur si l'ouverture est ouverte.\nLaissez l'id d'entité vide pour utiliser la détection automatique.", "data": { "window_sensor_entity_id": "Détecteur d'ouverture (entity id)", - "window_delay": "Délai avant extinction (secondes)", + "window_delay": "Délai de prise en compte à l'ouverture (secondes)", + "window_off_delay": "Délai de prise compte à la fermeture (secondes)", "window_auto_open_threshold": "Seuil haut de chute de température pour la détection automatique (en °/heure)", "window_auto_close_threshold": "Seuil bas de chute de température pour la fin de détection automatique (en °/heure)", "window_auto_max_duration": "Durée maximum d'une extinction automatique (en min)", @@ -132,7 +133,8 @@ }, "data_description": { "window_sensor_entity_id": "Laissez vide si vous n'avez de détecteur et pour utiliser la détection automatique", - "window_delay": "Le délai (en secondes) avant que le changement du détecteur soit pris en compte", + "window_delay": "Le délai (en secondes) avant que le changement du détecteur soit pris en compte lors de la détection d'une ouverture", + "window_off_delay": "Le délai (en secondes) avant que le changement du détecteur soit pris en compte lors de la détection d'une fermeture. Laissez vide pour utiliser le même délai à l'ouveture et à la fermeture", "window_auto_open_threshold": "Valeur recommandée: entre 3 et 10. Laissez vide si vous n'utilisez pas la détection automatique", "window_auto_close_threshold": "Valeur recommandée: 0. Laissez vide si vous n'utilisez pas la détection automatique", "window_auto_max_duration": "Valeur recommandée: 60 (1 heure). Laissez vide si vous n'utilisez pas la détection automatique", @@ -364,6 +366,7 @@ "data": { "window_sensor_entity_id": "Détecteur d'ouverture (entity id)", "window_delay": "Délai avant extinction (secondes)", + "window_off_delay": "Délai de prise compte à la fermeture (secondes)", "window_auto_open_threshold": "Seuil haut de chute de température pour la détection automatique (en °/heure)", "window_auto_close_threshold": "Seuil bas de chute de température pour la fin de détection automatique (en °/heure)", "window_auto_max_duration": "Durée maximum d'une extinction automatique (en min)", @@ -373,6 +376,7 @@ "data_description": { "window_sensor_entity_id": "Laissez vide si vous n'avez de détecteur et pour utiliser la détection automatique", "window_delay": "Le délai (en secondes) avant que le changement du détecteur soit pris en compte", + "window_off_delay": "Le délai (en secondes) avant que le changement du détecteur soit pris en compte lors de la détection d'une fermeture. Laissez vide pour utiliser le même délai à l'ouveture et à la fermeture", "window_auto_open_threshold": "Valeur recommandée: entre 3 et 10. Laissez vide si vous n'utilisez pas la détection automatique", "window_auto_close_threshold": "Valeur recommandée: 0. Laissez vide si vous n'utilisez pas la détection automatique", "window_auto_max_duration": "Valeur recommandée: 60 (1 heure). Laissez vide si vous n'utilisez pas la détection automatique", diff --git a/hacs.json b/hacs.json index e96916f..0a379a7 100644 --- a/hacs.json +++ b/hacs.json @@ -3,5 +3,5 @@ "content_in_root": false, "render_readme": true, "hide_default_branch": false, - "homeassistant": "2024.12.4" + "homeassistant": "2025.1.2" } \ No newline at end of file diff --git a/requirements_dev.txt b/requirements_dev.txt index cddf7fa..ea9f9f7 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1 +1 @@ -homeassistant==2024.12.4 +homeassistant==2025.1.2 diff --git a/tests/commons.py b/tests/commons.py index 7a39b94..3dde038 100644 --- a/tests/commons.py +++ b/tests/commons.py @@ -182,6 +182,7 @@ FULL_CENTRAL_CONFIG = { "comfort_ac_away_temp": 0, "boost_ac_away_temp": 30.7, CONF_WINDOW_DELAY: 15, + CONF_WINDOW_OFF_DELAY: 30, CONF_WINDOW_AUTO_OPEN_THRESHOLD: 4, CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 1, CONF_WINDOW_AUTO_MAX_DURATION: 31, diff --git a/tests/test_central_config.py b/tests/test_central_config.py index 2785157..e805dbe 100644 --- a/tests/test_central_config.py +++ b/tests/test_central_config.py @@ -452,7 +452,7 @@ async def test_over_switch_with_central_config_but_no_central_config( DOMAIN, context={"source": SOURCE_USER} ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["type"] == FlowResultType.FORM assert result["step_id"] == SOURCE_USER result = await hass.config_entries.flow.async_configure(