Compare commits

..

1 Commits

Author SHA1 Message Date
Jean-Marc Collin
03723375e2 Issue #121 - loop when underlying is slow 2023-10-14 08:28:04 +02:00
15 changed files with 95 additions and 280 deletions

View File

@@ -114,22 +114,18 @@ climate:
name: Underlying thermostat 4-1
heater: input_boolean.fake_heater_4climate1
target_sensor: input_number.fake_temperature_sensor1
ac_mode: true
- platform: generic_thermostat
name: Underlying thermostat 4-2
heater: input_boolean.fake_heater_4climate2
target_sensor: input_number.fake_temperature_sensor1
ac_mode: true
- platform: generic_thermostat
name: Underlying thermostat 4-3
heater: input_boolean.fake_heater_4climate3
target_sensor: input_number.fake_temperature_sensor1
ac_mode: true
- platform: generic_thermostat
name: Underlying thermostat 4-4
heater: input_boolean.fake_heater_4climate4
target_sensor: input_number.fake_temperature_sensor1
ac_mode: true
- platform: generic_thermostat
name: Underlying thermostat9
heater: input_boolean.fake_heater_switch3

View File

@@ -31,8 +31,6 @@ jobs:
- run: black .
tests:
# Tests don't run in Gitlab ci environment
if: 0
runs-on: "ubuntu-latest"
name: Run tests
steps:
@@ -43,10 +41,10 @@ jobs:
with:
python-version: "3.8"
- name: Install requirements
run: cd custom_components/versatile_thermostat && python3 -m pip install -r requirements_test.txt
run: python3 -m pip install -r requirements_test.txt
- name: Run tests
run: |
cd custom_components/versatile_thermostat && pytest \
pytest \
-qq \
--timeout=9 \
--durations=10 \

View File

@@ -26,7 +26,6 @@
- [Configurer la gestion de la puissance](#configurer-la-gestion-de-la-puissance)
- [Configurer la présence ou l'occupation](#configurer-la-présence-ou-loccupation)
- [Configuration avancée](#configuration-avancée)
- [Synthèse des paramètres](#synthèse-des-paramètres)
- [Exemples de réglage](#exemples-de-réglage)
- [Chauffage électrique](#chauffage-électrique)
- [Chauffage central (chauffage gaz ou fuel)](#chauffage-central-chauffage-gaz-ou-fuel)
@@ -66,7 +65,7 @@ Ce composant personnalisé pour Home Assistant est une mise à niveau et est une
> * **release majeure 2.0** : ajout du thermostat "over climate" permettant de transformer n'importe quel thermostat en Versatile Thermostat et lui ajouter toutes les fonctions de ce dernier.
# Merci pour la bière [buymecoffee](https://www.buymeacoffee.com/jmcollin78)
Un grand merci à @salabur, @pvince83, @bergoglio, @EPicLURcher, @Kriss1670, @maia pour les bières. Ca fait très plaisir.
Un grand merci à @salabur, @pvince83 and @bergoglio pour les bières. Ca fait très plaisir.
# Quand l'utiliser et ne pas l'utiliser
@@ -316,64 +315,6 @@ Voir [exemple de réglages](#examples-tuning) pour avoir des exemples de réglag
5. Pour un usage naturel, le ``security_default_on_percent`` doit être inférieur à ``security_min_on_percent``,
6. Lorsqu'un thermostat de type ``thermostat_over_climate`` passe en mode ``security`` il est éteint. Les paramètres ``security_min_on_percent`` et ``security_default_on_percent`` ne sont alors pas utilisés.
## Synthèse des paramètres
| Paramètre | Libellé | "over switch" | "over climate" |
| ----------| --------| --- | ---|
| ``name`` | Nom | X | X |
| ``thermostat_type`` | Type de thermostat | X | X |
| ``temperature_sensor_entity_id`` | Temperature sensor entity id | X | - |
| ``external_temperature_sensor_entity_id`` | Température exterieure sensor entity id | X | - |
| ``cycle_min`` | Durée du cycle (minutes) | X | X |
| ``temp_min`` | Température minimale permise | X | X |
| ``temp_max`` | Température maximale permise | X | X |
| ``device_power`` | Puissance de l'équipement | X | X |
| ``use_window_feature`` | Avec détection des ouvertures | X | X |
| ``use_motion_feature`` | Avec détection de mouvement | X | X |
| ``use_power_feature`` | Avec gestion de la puissance | X | X |
| ``use_presence_feature`` | Avec détection de présence | X | X |
| ``heater_entity1_id`` | 1er radiateur | X | - |
| ``heater_entity2_id`` | 2ème radiateur | X | - |
| ``heater_entity3_id`` | 3ème radiateur | X | - |
| ``heater_entity4_id`` | 4ème radiateur | X | - |
| ``proportional_function`` | Algorithme | X | - |
| ``climate_entity1_id`` | Thermostat sous-jacent | - | X |
| ``climate_entity2_id`` | 2ème thermostat sous-jacent | - | X |
| ``climate_entity3_id`` | 3ème thermostat sous-jacent | - | X |
| ``climate_entity4_id`` | 4ème thermostat sous-jacent | - | X |
| ``ac_mode`` | utilisation de l'air conditionné (AC) ? | - | X |
| ``tpi_coef_int`` | Coefficient à utiliser pour le delta de température interne | X | - |
| ``tpi_coef_ext`` | Coefficient à utiliser pour le delta de température externe | X | - |
| ``eco_temp`` | Température en preset Eco | X | X |
| ``comfort_temp`` | Température en preset Confort | X | X |
| ``boost_temp`` | Température en preset Boost | X | X |
| ``eco_ac_temp`` | Température en preset Eco en mode AC | X | X |
| ``comfort_ac_temp`` | Température en preset Confort en mode AC | X | X |
| ``boost_ac_temp`` | Température en preset Boost en mode AC | X | X |
| ``window_sensor_entity_id`` | Détecteur d'ouverture (entity id) | X | X |
| ``window_delay`` | Délai avant extinction (secondes) | X | X |
| ``window_auto_open_threshold`` | Seuil haut de chute de température pour la détection automatique (en °/min) | X | X |
| ``window_auto_close_threshold`` | Seuil bas de chute de température pour la fin de détection automatique (en °/min) | X | X |
| ``window_auto_max_duration`` | Durée maximum d'une extinction automatique (en min) | X | X |
| ``motion_sensor_entity_id`` | Détecteur de mouvement entity id | X | X |
| ``motion_delay`` | Délai avant changement (seconds) | X | X |
| ``motion_preset`` | Preset à utiliser si mouvement détecté | X | X |
| ``no_motion_preset`` | Preset à utiliser si pas de mouvement détecté | X | X |
| ``power_sensor_entity_id`` | Capteur de puissance totale (entity id) | X | X |
| ``max_power_sensor_entity_id`` | Capteur de puissance Max (entity id) | X | X |
| ``power_temp`` | Température si délestaqe | X | X |
| ``presence_sensor_entity_id`` | Capteur de présence entity id (true si quelqu'un est présent) | X | X |
| ``eco_away_temp`` | Température en preset Eco en cas d'absence | X | X |
| ``comfort_away_temp`` | Température en preset Comfort en cas d'absence | X | X |
| ``boost_away_temp`` | Température en preset Boost en cas d'absence | X | X |
| ``eco_ac_away_temp`` | Température en preset Eco en cas d'absence en mode AC | X | X |
| ``comfort_ac_away_temp`` | Température en preset Comfort en cas d'absence en mode AC | X | X |
| ``boost_ac_away_temp`` | Température en preset Boost en cas d'absence en mode AC | X | X |
| ``minimal_activation_delay`` | Délai minimal d'activation | X | - |
| ``security_delay_min`` | Délai maximal entre 2 mesures de températures | X | - |
| ``security_min_on_percent`` | Pourcentage minimal de puissance pour passer en mode sécurité | X | - |
| ``security_default_on_percent`` | Pourcentage de puissance a utiliser en mode securité | X | - |
# Exemples de réglage
## Chauffage électrique
@@ -510,17 +451,6 @@ target:
entity_id : climate.my_thermostat
```
Ou pour changer le pré-réglage du mode Air Conditionné (AC) ajoutez un préfixe `_ac`` au nom du preset comme ceci :
```
service: versatile_thermostat.set_preset_temperature
data:
preset: boost_ac
temperature: 25
temperature_away: 30
target:
entity_id: climate.my_thermostat
```
> ![Astuce](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_
- après un redémarrage, les préréglages sont réinitialisés à la température configurée. Si vous souhaitez que votre changement soit permanent, vous devez modifier le préréglage de la température dans la configuration de l'intégration.

View File

@@ -26,7 +26,6 @@
- [Configure the power management](#configure-the-power-management)
- [Configure the presence or occupancy](#configure-the-presence-or-occupancy)
- [Advanced configuration](#advanced-configuration)
- [Parameters synthesis](#parameters-synthesis)
- [Examples tuning](#examples-tuning)
- [Electrical heater](#electrical-heater)
- [Central heating (gaz or fuel heating system)](#central-heating-gaz-or-fuel-heating-system)
@@ -65,16 +64,16 @@ This custom component for Home Assistant is an upgrade and is a complete rewrite
> * **major release 2.0**: addition of the "over climate" thermostat allowing you to transform any thermostat into a Versatile Thermostat and add all the functions of the latter.
# Thanks for the beer [buymecoffee](https://www.buymeacoffee.com/jmcollin78)
Many thanks to @@salabur, @pvince83, @bergoglio, @EPicLURcher, @Kriss1670, @maia for the beers. It's very pleasing.
Many thanks to @salabur, @pvince83 and @bergoglio for the beers. It's very pleasing.
# When to use / not use
This thermostat can control 2 types of equipment:
1. a heater that only works in on/off mode (named ```thermostat_over_switch```). Versatile Thermostat will regulate the length of a heating cycle and the pauses in-between by controlling a binary on/off switch. This mode is e.g. suitable for an electrical radiator controlled by a switch. The minimum configuration required to use this type of thermostat is:
1. a heater that only works in on/off mode (named ```thermostat_over_switch```). The minimum configuration required to use this type of thermostat is:
- an equipment such as a radiator (a ```switch``` or equivalent),
- a temperature probe for the room (or an input_number),
- an external temperature sensor (think about weather integration if you don't have one)
2. another thermostat that has its own operating modes (named ```thermostat_over_climate```). Versatile Thermostat will regulate the target temperature of a climate entity. Common examples for this mode are the control of thermostatic radiator valves (TRV), air-conditions (AC), floor heating systems and pellet heating. For this type of thermostat, the minimum configuration requires:
2. another thermostat that has its own operating modes (named ```thermostat_over_climate```). For this type of thermostat, the minimum configuration requires:
- an equipment such as air conditioning which is controlled by its own ```climate``` type entity,
- a temperature probe for the room (or an input_number),
- an external temperature sensor (think about weather integration if you don't have one)
@@ -301,64 +300,6 @@ See [example tuning](#examples-tuning) for common tuning examples
5. For natural usage, the ``security_default_on_percent`` should be less than ``security_min_on_percent``,
6. When a ``thermostat_over_climate`` type thermostat goes into ``security`` mode it is turned off. The ``security_min_on_percent`` and ``security_default_on_percent`` parameters are then not used.
## Parameters synthesis
| Paramètre | Libellé | "over switch" | "over climate" |
| ----------| --------| --- | --- |
| ``name`` | Name | X | X |
| ``thermostat_type`` | Thermostat type | X | X |
| ``temperature_sensor_entity_id`` | Temperature sensor entity id | X | - |
| ``external_temperature_sensor_entity_id`` | External temperature sensor entity id | X | - |
| ``cycle_min`` | Cycle duration (minutes) | X | X |
| ``temp_min`` | Minimal temperature allowed | X | X |
| ``temp_max`` | Maximal temperature allowed | X | X |
| ``device_power`` | Device power | X | X |
| ``use_window_feature`` | Use window detection | X | X |
| ``use_motion_feature`` | Use motion detection | X | X |
| ``use_power_feature`` | Use power management | X | X |
| ``use_presence_feature`` | Use presence detection | X | X |
| ``heater_entity1_id`` | 1rst heater switch | X | - |
| ``heater_entity2_id`` | 2nd heater switch | X | - |
| ``heater_entity3_id`` | 3rd heater switch | X | - |
| ``heater_entity4_id`` | 4th heater switch | X | - |
| ``proportional_function`` | Algorithm | X | - |
| ``climate_entity1_id`` | 1rst underlying climate | - | X |
| ``climate_entity2_id`` | 2nd underlying climate | - | X |
| ``climate_entity3_id`` | 3rd underlying climate | - | X |
| ``climate_entity4_id`` | 4th underlying climate | - | X |
| ``ac_mode`` | Use the Air Conditioning (AC) mode | - | X |
| ``tpi_coef_int`` | Coefficient to use for internal temperature delta | X | - |
| ``tpi_coef_ext`` | Coefficient to use for external temperature delta | X | - |
| ``eco_temp`` | Temperature in Eco preset | X | X |
| ``comfort_temp`` | Temperature in Comfort preset | X | X |
| ``boost_temp`` | Temperature in Boost preset | X | X |
| ``eco_ac_temp`` | Temperature in Eco preset for AC mode | X | X |
| ``comfort_ac_temp`` | Temperature in Comfort preset for AC mode | X | X |
| ``boost_ac_temp`` | Temperature in Boost preset for AC mode | X | X |
| ``window_sensor_entity_id`` | Window sensor entity id | X | X |
| ``window_delay`` | Window sensor delay (seconds) | X | X |
| ``window_auto_open_threshold`` | Temperature decrease threshold for automatic window open detection (in °/min) | X | X |
| ``window_auto_close_threshold`` | Temperature increase threshold for end of automatic detection (in °/min) | X | X |
| ``window_auto_max_duration`` | Maximum duration of automatic window open detection (in min) | X | X |
| ``motion_sensor_entity_id`` | Motion sensor entity id | X | X |
| ``motion_delay`` | Motion delay (seconds) | X | X |
| ``motion_preset`` | Preset to use when motion is detected | X | X |
| ``no_motion_preset`` | Preset to use when no motion is detected | X | X |
| ``power_sensor_entity_id`` | Power sensor entity id | X | X |
| ``max_power_sensor_entity_id`` | Max power sensor entity id | X | X |
| ``power_temp`` | Temperature for Power shedding | X | X |
| ``presence_sensor_entity_id`` | Presence sensor entity id | X | X |
| ``eco_away_temp`` | Temperature in Eco preset when no presence | X | X |
| ``comfort_away_temp`` | Temperature in Comfort preset when no presence | X | X |
| ``boost_away_temp`` | Temperature in Boost preset when no presence | X | X |
| ``eco_ac_away_temp`` | Temperature in Eco preset when no presence in AC mode | X | X |
| ``comfort_ac_away_temp`` | Temperature in Comfort preset when no presence in AC mode | X | X |
| ``boost_ac_away_temp`` | Temperature in Boost preset when no presence in AC mode | X | X |
| ``minimal_activation_delay`` | Minimal activation delay | X | - |
| ``security_delay_min`` | Security delay (in minutes) | X | X |
| ``security_min_on_percent`` | Minimal power percent to enable security mode | X | X |
| ``security_default_on_percent`` | Power percent to use in security mode | X | X |
# Examples tuning
## Electrical heater
@@ -495,17 +436,6 @@ target:
entity_id: climate.my_thermostat
```
Or to change the preset of the AC mode, add _ac to the preset name like this:
```
service: versatile_thermostat.set_preset_temperature
data:
preset: boost_ac
temperature: 25
temperature_away: 30
target:
entity_id: climate.my_thermostat
```
> ![Tip](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_
- after a restart the preset are resetted to the configured temperature. If you want your change to be permanent you should modify the temperature preset into the confguration of the integration.

View File

@@ -3,7 +3,7 @@ import logging
from homeassistant.core import HomeAssistant, callback, Event
from homeassistant.const import STATE_ON, STATE_OFF
from homeassistant.const import STATE_ON
from homeassistant.components.binary_sensor import (
BinarySensorEntity,
@@ -133,14 +133,12 @@ class WindowBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_is_on
# Issue 120 - only take defined presence value
if self.my_climate.window_state in [STATE_ON, STATE_OFF] or self.my_climate.window_auto_state in [STATE_ON, STATE_OFF]:
self._attr_is_on = (
self.my_climate.window_state == STATE_ON
or self.my_climate.window_auto_state == STATE_ON
)
if old_state != self._attr_is_on:
self.async_write_ha_state()
self._attr_is_on = (
self.my_climate.window_state == STATE_ON
or self.my_climate.window_auto_state == STATE_ON
)
if old_state != self._attr_is_on:
self.async_write_ha_state()
return
@property
@@ -173,11 +171,9 @@ class MotionBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
"""Called when my climate have change"""
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_is_on
# Issue 120 - only take defined presence value
if self.my_climate.motion_state in [STATE_ON, STATE_OFF]:
self._attr_is_on = self.my_climate.motion_state == STATE_ON
if old_state != self._attr_is_on:
self.async_write_ha_state()
self._attr_is_on = self.my_climate.motion_state == STATE_ON
if old_state != self._attr_is_on:
self.async_write_ha_state()
return
@property
@@ -208,11 +204,9 @@ class PresenceBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_is_on
# Issue 120 - only take defined presence value
if self.my_climate.presence_state in [STATE_ON, STATE_OFF]:
self._attr_is_on = self.my_climate.presence_state == STATE_ON
if old_state != self._attr_is_on:
self.async_write_ha_state()
self._attr_is_on = self.my_climate.presence_state == STATE_ON
if old_state != self._attr_is_on:
self.async_write_ha_state()
return
@property

View File

@@ -956,6 +956,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
"""Return current operation."""
# Issue #114 - returns my current hvac_mode and not the underlying hvac_mode which could be different
# delta will be managed by climate_state_change event.
# TODO remove this when ok
# if self._is_over_climate:
# if one not OFF -> return it
# else OFF
@@ -1297,7 +1298,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
def reset_last_change_time(self, old_preset_mode=None):
"""Reset to now the last change time"""
self._last_change_time = datetime.now(tz=self._current_tz)
_LOGGER.debug("%s - last_change_time is now %s", self, self._last_change_time)
_LOGGER.warning("%s - last_change_time is now %s", self, self._last_change_time)
def reset_last_temperature_time(self, old_preset_mode=None):
"""Reset to now the last temperature time if conditions are satisfied"""
@@ -1597,22 +1598,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
@callback
async def _async_climate_changed(self, event):
"""Handle unerdlying climate state changes.
This method takes the underlying values and update the VTherm with them.
To avoid loops (issues #121 #101 #95 #99), we discard the event if it is received
less than 10 sec after the last command. What we want here is to take the values
from underlyings ONLY if someone have change directly on the underlying and not
as a return of the command. The only thing we take all the time is the HVACAction
which is important for feedaback and which cannot generates loops.
"""
async def end_climate_changed(changes):
""" To end the event management"""
if changes:
self.async_write_ha_state()
self.update_custom_attributes()
await self._async_control_heating()
"""Handle unerdlying climate state changes."""
new_state = event.data.get("new_state")
_LOGGER.debug("%s - _async_climate_changed new_state is %s", self, new_state)
if not new_state:
@@ -1633,10 +1619,10 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
else None
)
old_state_date_changed = old_state.last_changed if old_state and old_state.last_changed else None
old_state_date_updated = old_state.last_updated if old_state and old_state.last_updated else None
new_state_date_changed = new_state.last_changed if new_state and new_state.last_changed else None
new_state_date_updated = new_state.last_updated if new_state and new_state.last_updated else None
old_state_date_changed = old_state.last_changed
old_state_date_updated = old_state.last_updated
new_state_date_changed = new_state.last_changed
new_state_date_updated = new_state.last_updated
# Issue 99 - some AC turn hvac_mode=cool and hvac_action=idle when sending a HVACMode_OFF command
# Issue 114 - Remove this because hvac_mode is now managed by local _hvac_mode and use idle action as is
@@ -1653,9 +1639,26 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
old_hvac_action,
)
_LOGGER.debug("%s - last_change_time=%s old_state_date_changed=%s old_state_date_updated=%s new_state_date_changed=%s new_state_date_updated=%s", self, self._last_change_time, old_state_date_changed, old_state_date_updated, new_state_date_changed, new_state_date_updated)
_LOGGER.warning("%s - last_change_time=%s old_state_date_changed=%s old_state_date_updated=%s new_state_date_changed=%s new_state_date_updated=%s", self, self._last_change_time, old_state_date_changed, old_state_date_updated, new_state_date_changed, new_state_date_updated)
# Interpretation of hvac action
if new_hvac_mode in [
HVACMode.OFF,
HVACMode.HEAT,
HVACMode.COOL,
HVACMode.HEAT_COOL,
HVACMode.DRY,
HVACMode.AUTO,
HVACMode.FAN_ONLY,
None
] and self._hvac_mode != new_hvac_mode:
changes = True
self._hvac_mode = new_hvac_mode
# Update all underlyings state
if self._is_over_climate:
for under in self._underlyings:
await under.set_hvac_mode(new_hvac_mode)
# Interpretation of hvac
HVAC_ACTION_ON = [ # pylint: disable=invalid-name
HVACAction.COOLING,
HVACAction.DRYING,
@@ -1694,43 +1697,19 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
)
changes = True
# Issue #120 - Some TRV are chaning target temperature a very long time (6 sec) after the change.
# In that case a loop is possible if a user change multiple times during this 6 sec.
if new_state_date_updated and self._last_change_time:
delta = (new_state_date_updated - self._last_change_time).total_seconds()
if delta < 10:
_LOGGER.info("%s - underlying event is received less than 10 sec after command. Forget it to avoid loop", self
)
await end_climate_changed(changes)
return
if new_hvac_mode in [
HVACMode.OFF,
HVACMode.HEAT,
HVACMode.COOL,
HVACMode.HEAT_COOL,
HVACMode.DRY,
HVACMode.AUTO,
HVACMode.FAN_ONLY,
None
] and self._hvac_mode != new_hvac_mode:
changes = True
self._hvac_mode = new_hvac_mode
# Update all underlyings state
if self._is_over_climate:
for under in self._underlyings:
await under.set_hvac_mode(new_hvac_mode)
if not changes:
# try to manage new target temperature set if state
_LOGGER.debug("Do temperature check. temperature is %s, new_state.attributes is %s", self.target_temperature, new_state.attributes)
if self._is_over_climate and new_state.attributes and (new_target_temp := new_state.attributes.get("temperature")) and new_target_temp != self.target_temperature:
_LOGGER.info("%s - Target temp in underlying have change to %s", self, new_target_temp)
await self.async_set_temperature(temperature = new_target_temp)
_LOGGER.warning("%s - Target temp have change to %s", self, new_target_temp)
# TODO temporary removes the temperature changes for test
# await self.async_set_temperature(temperature = new_target_temp)
changes = True
await end_climate_changed(changes)
if changes:
self.async_write_ha_state()
self.update_custom_attributes()
await self._async_control_heating()
@callback
async def _async_update_temp(self, state: State):
@@ -2145,6 +2124,9 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
now - self._last_ext_temperature_mesure.replace(tzinfo=self._current_tz)
).total_seconds() / 60.0
# TODO before change:
# mode_cond = self._is_over_climate or self._hvac_mode != HVACMode.OFF
# fixed into this. Why if _is_over_climate we could into security even if HVACMode is OFF ?
mode_cond = self._hvac_mode != HVACMode.OFF
temp_cond: bool = (
@@ -2299,21 +2281,21 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
)
try:
under.startup()
except UnknownEntity:
except UnknownEntity as err:
# still not found, we an stop here
return False
raise err
# Check overpowering condition
# Not necessary for switch because each switch is checking at startup
overpowering: bool = await self.check_overpowering()
if overpowering:
_LOGGER.debug("%s - End of cycle (overpowering)", self)
return True
return
security: bool = await self.check_security()
if security and self._is_over_climate:
_LOGGER.debug("%s - End of cycle (security and over climate)", self)
return True
return
# Stop here if we are off
if self._hvac_mode == HVACMode.OFF:
@@ -2321,7 +2303,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
# A security to force stop heater if still active
if self._is_device_active:
await self._async_underlying_entity_turn_off()
return True
return
if not self._is_over_climate:
for under in self._underlyings:
@@ -2333,17 +2315,19 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
)
self.update_custom_attributes()
return True
def recalculate(self):
"""A utility function to force the calculation of a the algo and
update the custom attributes and write the state
"""
if self._is_over_climate:
self.update_custom_attributes()
return
_LOGGER.debug("%s - recalculate all", self)
if not self._is_over_climate:
self._prop_algorithm.calculate(
self._target_temp, self._cur_temp, self._cur_ext_temp
)
self._prop_algorithm.calculate(
self._target_temp, self._cur_temp, self._cur_ext_temp
)
self.update_custom_attributes()
self.async_write_ha_state()
@@ -2418,18 +2402,13 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
.astimezone(self._current_tz)
.isoformat(),
"timezone": str(self._current_tz),
"window_sensor_entity_id": self._window_sensor_entity_id,
"window_delay_sec": self._window_delay_sec,
"window_auto_open_threshold": self._window_auto_open_threshold,
"window_auto_close_threshold": self._window_auto_close_threshold,
"window_auto_max_duration": self._window_auto_max_duration,
"motion_sensor_entity_id": self._motion_sensor_entity_id,
"presence_sensor_entity_id": self._presence_sensor_entity_id,
"power_sensor_entity_id": self._power_sensor_entity_id,
"max_power_sensor_entity_id": self._max_power_sensor_entity_id,
}
if self._is_over_climate:
self._attr_extra_state_attributes["underlying_climate_0"] = self._underlyings[
self._attr_extra_state_attributes["underlying_climate_1"] = self._underlyings[
0
].entity_id
self._attr_extra_state_attributes["underlying_climate_1"] = self._underlyings[
@@ -2530,9 +2509,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
)
# If the changed preset is active, change the current temperature
# Issue #119 - reload new preset temperature also in ac mode
if preset.startswith(self._attr_preset_mode):
await self._async_set_preset_mode_internal(preset.rstrip(PRESET_AC_SUFFIX), force=True)
if self._attr_preset_mode == preset:
await self._async_set_preset_mode_internal(preset, force=True)
await self._async_control_heating(force=True)
async def service_set_security(self, delay_min, min_on_percent, default_on_percent):

View File

@@ -14,6 +14,6 @@
"quality_scale": "silver",
"requirements": [],
"ssdp": [],
"version": "3.5.3",
"version": "3.0.0",
"zeroconf": []
}

View File

@@ -1,2 +1,2 @@
homeassistant==2023.10.3
homeassistant==2023.10.1
ffmpeg

View File

@@ -1,5 +1,4 @@
# Warning: For automatic run of test in Gitlab CI, we must not include other things that pytest-homeassistant-custom-component
-r requirements_dev.txt
aiodiscover
# aiodiscover
ulid_transform
pytest-homeassistant-custom-component

View File

@@ -14,7 +14,7 @@
"cycle_min": "Cycle duration (minutes)",
"temp_min": "Minimal temperature allowed",
"temp_max": "Maximal temperature allowed",
"device_power": "Device power",
"device_power": "Device power (kW)",
"use_window_feature": "Use window detection",
"use_motion_feature": "Use motion detection",
"use_power_feature": "Use power management",
@@ -25,12 +25,12 @@
"title": "Linked entities",
"description": "Linked entities attributes",
"data": {
"heater_entity_id": "1rst heater switch",
"heater_entity_id": "Heater switch",
"heater_entity2_id": "2nd heater switch",
"heater_entity3_id": "3rd heater switch",
"heater_entity4_id": "4th heater switch",
"proportional_function": "Algorithm",
"climate_entity_id": "1rst underlying climate",
"climate_entity_id": "Underlying climate",
"climate_entity2_id": "2nd underlying climate",
"climate_entity3_id": "3rd underlying climate",
"climate_entity4_id": "4th underlying climate",
@@ -110,7 +110,7 @@
"title": "Presence management",
"description": "Presence management attributes.\nGives the a presence sensor of your home (true is someone is present).\nThen specify either the preset to use when presence sensor is false or the offset in temperature to apply.\nIf preset is given, the offset will not be used.\nLeave corresponding entity_id empty if not used.",
"data": {
"presence_sensor_entity_id": "Presence sensor entity id",
"presence_sensor_entity_id": "Presence sensor entity id (true is present)",
"eco_away_temp": "Temperature in Eco preset when no presence",
"comfort_away_temp": "Temperature in Comfort preset when no presence",
"boost_away_temp": "Temperature in Boost preset when no presence",
@@ -125,7 +125,7 @@
"data": {
"minimal_activation_delay": "Minimal activation delay",
"security_delay_min": "Security delay (in minutes)",
"security_min_on_percent": "Minimal power percent to enable security mode",
"security_min_on_percent": "Minimal power percent for security mode",
"security_default_on_percent": "Power percent to use in security mode"
},
"data_description": {

View File

@@ -62,9 +62,11 @@ async def test_bug_56(
# try to call _async_control_heating
try:
ret = await entity._async_control_heating()
await entity._async_control_heating()
# an exception should be send
assert ret is False
assert False
except UnknownEntity:
pass
except Exception: # pylint: disable=broad-exception-caught
assert False
@@ -528,16 +530,9 @@ async def test_bug_101(
await entity.async_set_preset_mode(PRESET_COMFORT)
assert entity.preset_mode == PRESET_COMFORT
# 2. Change the target temp of underlying thermostat at now -> the event will be disgarded because to fast (to avoid loop cf issue 121)
# 2. Change the target temp of underlying thermostat
await send_climate_change_event_with_temperature(entity, HVACMode.HEAT, HVACMode.HEAT, HVACAction.OFF, HVACAction.OFF, now, 12.75)
# Should NOT have been switched to Manual preset
assert entity.target_temperature == 17
assert entity.preset_mode is PRESET_COMFORT
# 2. Change the target temp of underlying thermostat at 11 sec later -> the event will be taken
# Wait 11 sec
event_timestamp = now + timedelta(seconds=11)
await send_climate_change_event_with_temperature(entity, HVACMode.HEAT, HVACMode.HEAT, HVACAction.OFF, HVACAction.OFF, event_timestamp, 12.75)
# Should have been switched to Manual preset
assert entity.target_temperature == 12.75
assert entity.preset_mode is PRESET_NONE

View File

@@ -163,7 +163,7 @@ async def test_one_switch_cycle(
await asyncio.sleep(0.1)
assert mock_heater_on.call_count == 1
# normal ? assert entity.underlying_entity(0)._should_relaunch_control_heating is False
# TODO normal ? assert entity.underlying_entity(0)._should_relaunch_control_heating is False
# Simulate the end of heater on cycle
event_timestamp = now - timedelta(minutes=3)
@@ -522,9 +522,7 @@ async def test_multiple_climates_underlying_changes(
), patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
) as mock_underlying_set_hvac_mode:
# Wait 11 sec so that the event will not be discarded
event_timestamp = now + timedelta(seconds=11)
await send_climate_change_event(entity, HVACMode.OFF, HVACMode.HEAT, HVACAction.OFF, HVACAction.HEATING, event_timestamp)
await send_climate_change_event(entity, HVACMode.OFF, HVACMode.HEAT, HVACAction.OFF, HVACAction.HEATING, now)
# Should be call for all Switch
assert mock_underlying_set_hvac_mode.call_count == 4
@@ -545,9 +543,7 @@ async def test_multiple_climates_underlying_changes(
# notice that there is no need of return_value=HVACAction.IDLE because this is not a function but a property
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.hvac_action", HVACAction.IDLE
) as mock_underlying_get_hvac_action:
# Wait 11 sec so that the event will not be discarded
event_timestamp = now + timedelta(seconds=11)
await send_climate_change_event(entity, HVACMode.HEAT, HVACMode.OFF, HVACAction.IDLE, HVACAction.OFF, event_timestamp)
await send_climate_change_event(entity, HVACMode.HEAT, HVACMode.OFF, HVACAction.IDLE, HVACAction.OFF, now)
# Should be call for all Switch
assert mock_underlying_set_hvac_mode.call_count == 4

View File

@@ -14,7 +14,7 @@
"cycle_min": "Cycle duration (minutes)",
"temp_min": "Minimal temperature allowed",
"temp_max": "Maximal temperature allowed",
"device_power": "Device power",
"device_power": "Device power (kW)",
"use_window_feature": "Use window detection",
"use_motion_feature": "Use motion detection",
"use_power_feature": "Use power management",
@@ -25,12 +25,12 @@
"title": "Linked entities",
"description": "Linked entities attributes",
"data": {
"heater_entity_id": "1rst heater switch",
"heater_entity_id": "Heater switch",
"heater_entity2_id": "2nd heater switch",
"heater_entity3_id": "3rd heater switch",
"heater_entity4_id": "4th heater switch",
"proportional_function": "Algorithm",
"climate_entity_id": "1rst underlying climate",
"climate_entity_id": "Underlying climate",
"climate_entity2_id": "2nd underlying climate",
"climate_entity3_id": "3rd underlying climate",
"climate_entity4_id": "4th underlying climate",
@@ -110,7 +110,7 @@
"title": "Presence management",
"description": "Presence management attributes.\nGives the a presence sensor of your home (true is someone is present).\nThen specify either the preset to use when presence sensor is false or the offset in temperature to apply.\nIf preset is given, the offset will not be used.\nLeave corresponding entity_id empty if not used.",
"data": {
"presence_sensor_entity_id": "Presence sensor entity id",
"presence_sensor_entity_id": "Presence sensor entity id (true is present)",
"eco_away_temp": "Temperature in Eco preset when no presence",
"comfort_away_temp": "Temperature in Comfort preset when no presence",
"boost_away_temp": "Temperature in Boost preset when no presence",
@@ -125,7 +125,7 @@
"data": {
"minimal_activation_delay": "Minimal activation delay",
"security_delay_min": "Security delay (in minutes)",
"security_min_on_percent": "Minimal power percent to enable security mode",
"security_min_on_percent": "Minimal power percent for security mode",
"security_default_on_percent": "Power percent to use in security mode"
},
"data_description": {

View File

@@ -8,9 +8,8 @@
"description": "Principaux attributs obligatoires",
"data": {
"name": "Nom",
"thermostat_type": "Type de thermostat",
"temperature_sensor_entity_id": "Température sensor entity id",
"external_temperature_sensor_entity_id": "Température exterieure sensor entity id",
"external_temperature_sensor_entity_id": "Temperature exterieure sensor entity id",
"cycle_min": "Durée du cycle (minutes)",
"temp_min": "Température minimale permise",
"temp_max": "Température maximale permise",
@@ -75,7 +74,7 @@
"data": {
"window_sensor_entity_id": "Détecteur d'ouverture (entity id)",
"window_delay": "Délai avant extinction (secondes)",
"window_auto_open_threshold": "Seuil haut de chute de température pour la détection automatique (en °/min)",
"window_auto_open_threshold": "seuil haut de chute de température pour la détection automatique (en °/min)",
"window_auto_close_threshold": "Seuil bas de chute de température pour la fin de détection automatique (en °/min)",
"window_auto_max_duration": "Durée maximum d'une extinction automatique (en min)"
},

View File

@@ -3,5 +3,5 @@
"content_in_root": false,
"render_readme": true,
"hide_default_branch": false,
"homeassistant": "2023.10.3"
"homeassistant": "2023.7.3"
}