diff --git a/custom_components/versatile_thermostat/sensor.py b/custom_components/versatile_thermostat/sensor.py index 79bad92..78fd8d8 100644 --- a/custom_components/versatile_thermostat/sensor.py +++ b/custom_components/versatile_thermostat/sensor.py @@ -506,17 +506,15 @@ class RegulatedTemperatureSensor(VersatileThermostatBaseEntity, SensorEntity): """Called when my climate have change""" _LOGGER.debug("%s - climate state change", self._attr_unique_id) - if math.isnan(self.my_climate.regulated_target_temp) or math.isinf( - self.my_climate.regulated_target_temp - ): - raise ValueError( - f"Sensor has illegal state {self.my_climate.regulated_target_temp}" - ) + new_temp = self.my_climate.regulated_target_temp + if new_temp is None: + return + + if math.isnan(new_temp) or math.isinf(new_temp): + raise ValueError(f"Sensor has illegal state {new_temp}") old_state = self._attr_native_value - self._attr_native_value = round( - self.my_climate.regulated_target_temp, self.suggested_display_precision - ) + self._attr_native_value = round(new_temp, self.suggested_display_precision) if old_state != self._attr_native_value: self.async_write_ha_state() return @@ -559,15 +557,15 @@ class EMATemperatureSensor(VersatileThermostatBaseEntity, SensorEntity): """Called when my climate have change""" _LOGGER.debug("%s - climate state change", self._attr_unique_id) - if math.isnan(self.my_climate.ema_temperature) or math.isinf( - self.my_climate.ema_temperature - ): - raise ValueError( - f"Sensor has illegal state {self.my_climate.ema_temperature}" - ) + new_ema = self.my_climate.ema_temperature + if new_ema is None: + return + + if math.isnan(new_ema) or math.isinf(new_ema): + raise ValueError(f"Sensor has illegal state {new_ema}") old_state = self._attr_native_value - self._attr_native_value = self.my_climate.ema_temperature + self._attr_native_value = new_ema if old_state != self._attr_native_value: self.async_write_ha_state() return diff --git a/tests/test_auto_regulation.py b/tests/test_auto_regulation.py index 327c7f2..cce901a 100644 --- a/tests/test_auto_regulation.py +++ b/tests/test_auto_regulation.py @@ -363,12 +363,12 @@ async def test_over_climate_regulation_limitations( "custom_components.versatile_thermostat.commons.NowClass.get_now", return_value=event_timestamp, ): - await send_temperature_change_event(entity, 16, event_timestamp) + await send_temperature_change_event(entity, 15, event_timestamp) await send_ext_temperature_change_event(entity, 12, event_timestamp) # the regulated should have been done assert entity.regulated_target_temp != old_regulated_temp assert entity.regulated_target_temp >= entity.target_temperature assert ( - entity.regulated_target_temp == 17 + 0.5 + entity.regulated_target_temp == 17 + 1.5 ) # 0.7 without round_to_nearest diff --git a/tests/test_open_window_algo.py b/tests/test_open_window_algo.py index fea12eb..b382087 100644 --- a/tests/test_open_window_algo.py +++ b/tests/test_open_window_algo.py @@ -2,7 +2,9 @@ """ Test the OpenWindow algorithm """ from datetime import datetime, timedelta -from custom_components.versatile_thermostat.open_window_algorithm import WindowOpenDetectionAlgorithm +from custom_components.versatile_thermostat.open_window_algorithm import ( + WindowOpenDetectionAlgorithm, +) from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import @@ -19,18 +21,28 @@ async def test_open_window_algo( tz = get_tz(hass) # pylint: disable=invalid-name now = datetime.now(tz) - event_timestamp = now - timedelta(minutes=5) + event_timestamp = now - timedelta(minutes=10) last_slope = the_algo.add_temp_measurement( temperature=10, datetime_measure=event_timestamp ) - # We need at least 2 measurement + # We need at least 4 measurement assert last_slope is None assert the_algo.last_slope is None assert the_algo.is_window_close_detected() is False assert the_algo.is_window_open_detected() is False - event_timestamp = now - timedelta(minutes=4) + event_timestamp = now - timedelta(minutes=9) + last_slope = the_algo.add_temp_measurement( + temperature=10, datetime_measure=event_timestamp + ) + + event_timestamp = now - timedelta(minutes=8) + last_slope = the_algo.add_temp_measurement( + temperature=10, datetime_measure=event_timestamp + ) + + event_timestamp = now - timedelta(minutes=7) last_slope = the_algo.add_temp_measurement( temperature=10, datetime_measure=event_timestamp ) @@ -41,19 +53,19 @@ async def test_open_window_algo( assert the_algo.is_window_close_detected() is True assert the_algo.is_window_open_detected() is False - event_timestamp = now - timedelta(minutes=3) + event_timestamp = now - timedelta(minutes=6) last_slope = the_algo.add_temp_measurement( temperature=9, datetime_measure=event_timestamp ) # A slope is calculated - assert last_slope == -0.5 - assert the_algo.last_slope == -0.5 + assert last_slope == -0.8 + assert the_algo.last_slope == -0.8 assert the_algo.is_window_close_detected() is False assert the_algo.is_window_open_detected() is False # A new temperature with 2 degre less in one minute (value will be rejected) - event_timestamp = now - timedelta(minutes=2) + event_timestamp = now - timedelta(minutes=5) last_slope = the_algo.add_temp_measurement( temperature=7, datetime_measure=event_timestamp ) @@ -65,7 +77,7 @@ async def test_open_window_algo( assert the_algo.is_window_open_detected() is True # A new temperature with 1 degre less - event_timestamp = now - timedelta(minutes=1) + event_timestamp = now - timedelta(minutes=4) last_slope = the_algo.add_temp_measurement( temperature=6, datetime_measure=event_timestamp ) @@ -77,7 +89,7 @@ async def test_open_window_algo( assert the_algo.is_window_open_detected() is True # A new temperature with 0 degre less - event_timestamp = now - timedelta(minutes=0) + event_timestamp = now - timedelta(minutes=3) last_slope = the_algo.add_temp_measurement( temperature=6, datetime_measure=event_timestamp ) @@ -89,7 +101,7 @@ async def test_open_window_algo( assert the_algo.is_window_open_detected() is False # A new temperature with 1 degre more - event_timestamp = now + timedelta(minutes=1) + event_timestamp = now + timedelta(minutes=2) last_slope = the_algo.add_temp_measurement( temperature=7, datetime_measure=event_timestamp ) diff --git a/tests/test_window.py b/tests/test_window.py index c651eae..82e4ab1 100644 --- a/tests/test_window.py +++ b/tests/test_window.py @@ -477,6 +477,14 @@ async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_st assert entity.window_state is STATE_OFF + # Initialize the slope algo with 2 measurements + # event_timestamp = now - timedelta(minutes=9) + # await send_temperature_change_event(entity, 19, event_timestamp) + # event_timestamp = now - timedelta(minutes=8) + # await send_temperature_change_event(entity, 19, event_timestamp) + # event_timestamp = now - timedelta(minutes=7) + # await send_temperature_change_event(entity, 19, event_timestamp) + # Make the temperature down with patch( "custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event" @@ -486,6 +494,7 @@ async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_st "custom_components.versatile_thermostat.underlyings.UnderlyingClimate.is_device_active", return_value=True, ): + # This is the 3rd measurment. Slope is not ready event_timestamp = now - timedelta(minutes=4) await send_temperature_change_event(entity, 19, event_timestamp) @@ -531,7 +540,7 @@ async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_st assert entity.window_auto_state == STATE_ON assert entity.hvac_mode is HVACMode.OFF - # Waits for automatic disable + # Waits for automatic disable with patch( "custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event" ) as mock_send_event, patch(