Compare commits

...

4 Commits

Author SHA1 Message Date
Jean-Marc Collin 2bebe3e210 Issue #95 - the integration would switch ac on and off rapidly and lock up home assistant if outside temp is NaN 2023-08-05 20:02:54 +02:00
Jean-Marc Collin aa3b87762d FIX AC climate stops even if already stopped 2023-07-30 21:25:20 +02:00
Jean-Marc Collin f4cabbf2c0 FIX unit tests 2023-07-30 18:09:42 +02:00
Jean-Marc Collin 24b59e545b FIX cancel_timer await 2023-07-29 11:22:50 +02:00
5 changed files with 203 additions and 201 deletions
+9 -9
View File
@@ -141,9 +141,9 @@ template:
device_class: energy device_class: energy
state_class: total_increasing state_class: total_increasing
state: > state: >
{% set energy = state_attr('climate.thermostat_switch_1', 'total_energy') %} {% set energy = state_attr('climate.thermostat_switch_1', 'total_energy') | float(default=-1) %}
{% if energy == 'unavailable' or energy is none%}unavailable{% else %} {% if energy < 0 %}{{none}}{% else %}
{{ ((energy | float) / 1.0) | round(2, default=0) }} {{ energy | round(2, default=0) }}
{% endif %} {% endif %}
- name: "Total énergie climate 2" - name: "Total énergie climate 2"
unique_id: total_energie_climate2 unique_id: total_energie_climate2
@@ -151,9 +151,9 @@ template:
device_class: energy device_class: energy
state_class: total_increasing state_class: total_increasing
state: > state: >
{% set energy = state_attr('climate.thermostat_climate_2', 'total_energy') %} {% set energy = state_attr('climate.thermostat_climate_2', 'total_energy') | float(default=-1) %}
{% if energy == 'unavailable' or energy is none%}unavailable{% else %} {% if energy < 0 %}{{none}}{% else %}
{{ ((energy | float) / 1.0) | round(2, default=0) }} {{ energy | round(2, default=0) }}
{% endif %} {% endif %}
- name: "Total énergie chambre" - name: "Total énergie chambre"
unique_id: total_energie_chambre unique_id: total_energie_chambre
@@ -161,9 +161,9 @@ template:
device_class: energy device_class: energy
state_class: total_increasing state_class: total_increasing
state: > state: >
{% set energy = state_attr('climate.thermostat_chambre', 'total_energy') %} {% set energy = state_attr('climate.thermostat_chambre', 'total_energy') | float(default=-1) %}
{% if energy == 'unavailable' or energy is none%}unavailable{% else %} {% if energy < 0 %}{{none}}{% else %}
{{ ((energy | float) / 1.0) | round(2, default=0) }} {{ energy | round(2, default=0) }}
{% endif %} {% endif %}
switch: switch:
@@ -1611,6 +1611,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
HVACMode.DRY, HVACMode.DRY,
HVACMode.AUTO, HVACMode.AUTO,
HVACMode.FAN_ONLY, HVACMode.FAN_ONLY,
None
]: ]:
self._hvac_mode = new_state.state self._hvac_mode = new_state.state
@@ -2098,9 +2099,17 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
switch_cond, switch_cond,
) )
ret = False shouldClimateBeInSecurity = temp_cond and climate_cond
if mode_cond and temp_cond and climate_cond: shouldSwitchBeInSecurity = temp_cond and switch_cond
if not self._security_state: shouldBeInSecurity = shouldClimateBeInSecurity or shouldSwitchBeInSecurity
shouldStartSecurity = mode_cond and not self._security_state and shouldBeInSecurity
# attr_preset_mode is not necessary normaly. It is just here to be sure
shouldStopSecurity = self._security_state and not shouldBeInSecurity and self._attr_preset_mode == PRESET_SECURITY
# Logging and event
if shouldStartSecurity:
if shouldClimateBeInSecurity:
_LOGGER.warning( _LOGGER.warning(
"%s - No temperature received for more than %.1f minutes (dt=%.1f, dext=%.1f) and underlying climate is %s. Set it into security mode", "%s - No temperature received for more than %.1f minutes (dt=%.1f, dext=%.1f) and underlying climate is %s. Set it into security mode",
self, self,
@@ -2109,10 +2118,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
delta_ext_temp, delta_ext_temp,
self.hvac_action, self.hvac_action,
) )
ret = True elif shouldSwitchBeInSecurity:
if mode_cond and temp_cond and switch_cond:
if not self._security_state:
_LOGGER.warning( _LOGGER.warning(
"%s - No temperature received for more than %.1f minutes (dt=%.1f, dext=%.1f) and on_percent (%.2f) is over defined value (%.2f). Set it into security mode", "%s - No temperature received for more than %.1f minutes (dt=%.1f, dext=%.1f) and on_percent (%.2f) is over defined value (%.2f). Set it into security mode",
self, self,
@@ -2122,9 +2128,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._prop_algorithm.on_percent, self._prop_algorithm.on_percent,
self._security_min_on_percent, self._security_min_on_percent,
) )
ret = True
if mode_cond and temp_cond and not self._security_state:
self.send_event( self.send_event(
EventType.TEMPERATURE_EVENT, EventType.TEMPERATURE_EVENT,
{ {
@@ -2140,8 +2144,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
}, },
) )
if not self._security_state and ret: if shouldStartSecurity:
self._security_state = ret self._security_state = True
self.save_hvac_mode() self.save_hvac_mode()
self.save_preset_mode() self.save_preset_mode()
await self._async_set_preset_mode_internal(PRESET_SECURITY) await self._async_set_preset_mode_internal(PRESET_SECURITY)
@@ -2167,18 +2171,14 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
}, },
) )
if ( if shouldStopSecurity:
self._security_state
and self._attr_preset_mode == PRESET_SECURITY
and not ret
):
_LOGGER.warning( _LOGGER.warning(
"%s - End of security mode. restoring hvac_mode to %s and preset_mode to %s", "%s - End of security mode. restoring hvac_mode to %s and preset_mode to %s",
self, self,
self._saved_hvac_mode, self._saved_hvac_mode,
self._saved_preset_mode, self._saved_preset_mode,
) )
self._security_state = ret self._security_state = False
# Restore hvac_mode if previously saved # Restore hvac_mode if previously saved
if self._is_over_climate or self._security_default_on_percent <= 0.0: if self._is_over_climate or self._security_default_on_percent <= 0.0:
await self.restore_hvac_mode(False) await self.restore_hvac_mode(False)
@@ -2201,7 +2201,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
}, },
) )
return ret return shouldBeInSecurity
async def _async_control_heating(self, force=False, _=None): async def _async_control_heating(self, force=False, _=None):
"""The main function used to run the calculation at each cycle""" """The main function used to run the calculation at each cycle"""
@@ -15,6 +15,7 @@ from custom_components.versatile_thermostat.const import (
CONF_THERMOSTAT_CLIMATE, CONF_THERMOSTAT_CLIMATE,
CONF_THERMOSTAT_SWITCH, CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_TYPE, CONF_THERMOSTAT_TYPE,
CONF_AC_MODE,
CONF_TEMP_SENSOR, CONF_TEMP_SENSOR,
CONF_EXTERNAL_TEMP_SENSOR, CONF_EXTERNAL_TEMP_SENSOR,
CONF_CYCLE_MIN, CONF_CYCLE_MIN,
@@ -112,6 +113,7 @@ MOCK_TH_OVER_SWITCH_TPI_CONFIG = {
MOCK_TH_OVER_CLIMATE_TYPE_CONFIG = { MOCK_TH_OVER_CLIMATE_TYPE_CONFIG = {
CONF_CLIMATE: "climate.mock_climate", CONF_CLIMATE: "climate.mock_climate",
CONF_AC_MODE: False,
} }
MOCK_PRESETS_CONFIG = { MOCK_PRESETS_CONFIG = {
@@ -94,7 +94,7 @@ async def test_window_management_time_not_enough(
await try_window_condition(None) await try_window_condition(None)
assert entity.window_state == STATE_OFF assert entity.window_state == STATE_OFF
await entity.remove_thermostat() entity.remove_thermostat()
@pytest.mark.parametrize("expected_lingering_tasks", [True]) @pytest.mark.parametrize("expected_lingering_tasks", [True])
@@ -234,7 +234,7 @@ async def test_window_management_time_enough(
assert entity.preset_mode is PRESET_BOOST assert entity.preset_mode is PRESET_BOOST
# Clean the entity # Clean the entity
await entity.remove_thermostat() entity.remove_thermostat()
@pytest.mark.parametrize("expected_lingering_tasks", [True]) @pytest.mark.parametrize("expected_lingering_tasks", [True])
@@ -418,7 +418,7 @@ async def test_window_auto_fast(hass: HomeAssistant, skip_hass_states_is_state):
assert entity.hvac_mode is HVACMode.HEAT assert entity.hvac_mode is HVACMode.HEAT
# Clean the entity # Clean the entity
await entity.remove_thermostat() entity.remove_thermostat()
@pytest.mark.parametrize("expected_lingering_tasks", [True]) @pytest.mark.parametrize("expected_lingering_tasks", [True])
@@ -561,7 +561,7 @@ async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_st
assert entity.preset_mode is PRESET_BOOST assert entity.preset_mode is PRESET_BOOST
# Clean the entity # Clean the entity
await entity.remove_thermostat() entity.remove_thermostat()
@pytest.mark.parametrize("expected_lingering_tasks", [True]) @pytest.mark.parametrize("expected_lingering_tasks", [True])
@@ -670,4 +670,4 @@ async def test_window_auto_no_on_percent(
assert entity.hvac_mode is HVACMode.HEAT assert entity.hvac_mode is HVACMode.HEAT
# Clean the entity # Clean the entity
await entity.remove_thermostat() entity.remove_thermostat()
@@ -275,7 +275,7 @@ class UnderlyingSwitch(UnderlyingEntity):
self._on_time_sec, self._on_time_sec,
) )
await self._cancel_cycle() self._cancel_cycle()
if self._hvac_mode == HVACMode.OFF: if self._hvac_mode == HVACMode.OFF:
_LOGGER.debug("%s - End of cycle (HVAC_MODE_OFF - 2)", self) _LOGGER.debug("%s - End of cycle (HVAC_MODE_OFF - 2)", self)
@@ -327,7 +327,7 @@ class UnderlyingSwitch(UnderlyingEntity):
self._should_relaunch_control_heating, self._should_relaunch_control_heating,
self._off_time_sec, self._off_time_sec,
) )
await self._cancel_cycle() self._cancel_cycle()
if self._hvac_mode == HVACMode.OFF: if self._hvac_mode == HVACMode.OFF:
_LOGGER.debug("%s - End of cycle (HVAC_MODE_OFF - 2)", self) _LOGGER.debug("%s - End of cycle (HVAC_MODE_OFF - 2)", self)
@@ -446,7 +446,7 @@ class UnderlyingClimate(UnderlyingEntity):
def is_device_active(self): def is_device_active(self):
"""If the toggleable device is currently active.""" """If the toggleable device is currently active."""
if self.is_initialized: if self.is_initialized:
return self._underlying_climate.hvac_action not in [ return self._underlying_climate.hvac_mode != HVACMode.OFF and self._underlying_climate.hvac_action not in [
HVACAction.IDLE, HVACAction.IDLE,
HVACAction.OFF, HVACAction.OFF,
] ]