Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d2829bb951 | |||
| cd50c9b6e8 | |||
| b6f52bcc1b | |||
| 5df77a1f74 |
@@ -25,7 +25,11 @@
|
|||||||
"ms-python.pylint",
|
"ms-python.pylint",
|
||||||
"ferrierbenjamin.fold-unfold-all-icone",
|
"ferrierbenjamin.fold-unfold-all-icone",
|
||||||
"ms-python.isort",
|
"ms-python.isort",
|
||||||
"LittleFoxTeam.vscode-python-test-adapter"
|
"LittleFoxTeam.vscode-python-test-adapter",
|
||||||
|
"donjayamanne.githistory",
|
||||||
|
"waderyan.gitblame",
|
||||||
|
"keesschollaart.vscode-home-assistant",
|
||||||
|
"vscode.markdown-math"
|
||||||
],
|
],
|
||||||
// "mounts": [
|
// "mounts": [
|
||||||
// "source=${localWorkspaceFolder}/.devcontainer/configuration.yaml,target=${localWorkspaceFolder}/config/www/community/,type=bind,consistency=cached",
|
// "source=${localWorkspaceFolder}/.devcontainer/configuration.yaml,target=${localWorkspaceFolder}/config/www/community/,type=bind,consistency=cached",
|
||||||
|
|||||||
@@ -131,8 +131,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
|
|
||||||
# The list of VersatileThermostat entities
|
# The list of VersatileThermostat entities
|
||||||
_hass: HomeAssistant
|
_hass: HomeAssistant
|
||||||
_last_temperature_mesure: datetime
|
_last_temperature_measure: datetime
|
||||||
_last_ext_temperature_mesure: datetime
|
_last_ext_temperature_measure: datetime
|
||||||
_total_energy: float
|
_total_energy: float
|
||||||
_overpowering_state: bool
|
_overpowering_state: bool
|
||||||
_window_state: bool
|
_window_state: bool
|
||||||
@@ -173,6 +173,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
"timezone",
|
"timezone",
|
||||||
"window_sensor_entity_id",
|
"window_sensor_entity_id",
|
||||||
"window_delay_sec",
|
"window_delay_sec",
|
||||||
|
"window_auto_enabled",
|
||||||
"window_auto_open_threshold",
|
"window_auto_open_threshold",
|
||||||
"window_auto_close_threshold",
|
"window_auto_close_threshold",
|
||||||
"window_auto_max_duration",
|
"window_auto_max_duration",
|
||||||
@@ -216,8 +217,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self._motion_call_cancel = None
|
self._motion_call_cancel = None
|
||||||
self._cur_temp = None
|
self._cur_temp = None
|
||||||
self._ac_mode = None
|
self._ac_mode = None
|
||||||
self._last_ext_temperature_mesure = None
|
self._last_ext_temperature_measure = None
|
||||||
self._last_temperature_mesure = None
|
self._last_temperature_measure = None
|
||||||
self._cur_ext_temp = None
|
self._cur_ext_temp = None
|
||||||
self._presence_state = None
|
self._presence_state = None
|
||||||
self._overpowering_state = None
|
self._overpowering_state = None
|
||||||
@@ -428,8 +429,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
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_mesure = datetime.now(tz=self._current_tz)
|
self._last_temperature_measure = datetime.now(tz=self._current_tz)
|
||||||
self._last_ext_temperature_mesure = datetime.now(tz=self._current_tz)
|
self._last_ext_temperature_measure = datetime.now(tz=self._current_tz)
|
||||||
self._security_state = False
|
self._security_state = False
|
||||||
|
|
||||||
# Initiate the ProportionalAlgorithm
|
# Initiate the ProportionalAlgorithm
|
||||||
@@ -447,8 +448,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
if len(presets):
|
if len(presets):
|
||||||
self._support_flags = SUPPORT_FLAGS | ClimateEntityFeature.PRESET_MODE
|
self._support_flags = SUPPORT_FLAGS | ClimateEntityFeature.PRESET_MODE
|
||||||
|
|
||||||
for key, val in CONF_PRESETS.items():
|
for key, _ in CONF_PRESETS.items():
|
||||||
if val != 0.0:
|
if self.find_preset_temp(key) > 0:
|
||||||
self._attr_preset_modes.append(key)
|
self._attr_preset_modes.append(key)
|
||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
@@ -1016,14 +1017,14 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
return self._prop_algorithm
|
return self._prop_algorithm
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def last_temperature_mesure(self) -> datetime | None:
|
def last_temperature_measure(self) -> datetime | None:
|
||||||
"""Get the last temperature datetime"""
|
"""Get the last temperature datetime"""
|
||||||
return self._last_temperature_mesure
|
return self._last_temperature_measure
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def last_ext_temperature_mesure(self) -> datetime | None:
|
def last_ext_temperature_measure(self) -> datetime | None:
|
||||||
"""Get the last external temperature datetime"""
|
"""Get the last external temperature datetime"""
|
||||||
return self._last_ext_temperature_mesure
|
return self._last_ext_temperature_measure
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def preset_mode(self) -> str | None:
|
def preset_mode(self) -> str | None:
|
||||||
@@ -1036,7 +1037,6 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
@property
|
@property
|
||||||
def preset_modes(self) -> list[str] | None:
|
def preset_modes(self) -> list[str] | None:
|
||||||
"""Return a list of available preset modes.
|
"""Return a list of available preset modes.
|
||||||
|
|
||||||
Requires ClimateEntityFeature.PRESET_MODE.
|
Requires ClimateEntityFeature.PRESET_MODE.
|
||||||
"""
|
"""
|
||||||
return self._attr_preset_modes
|
return self._attr_preset_modes
|
||||||
@@ -1190,18 +1190,31 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self._attr_preset_mode not in HIDDEN_PRESETS
|
self._attr_preset_mode not in HIDDEN_PRESETS
|
||||||
and old_preset_mode not in HIDDEN_PRESETS
|
and old_preset_mode not in HIDDEN_PRESETS
|
||||||
):
|
):
|
||||||
self._last_temperature_mesure = (
|
self._last_temperature_measure = (
|
||||||
self._last_ext_temperature_mesure
|
self._last_ext_temperature_measure
|
||||||
) = datetime.now(tz=self._current_tz)
|
) = datetime.now(tz=self._current_tz)
|
||||||
|
|
||||||
def find_preset_temp(self, preset_mode):
|
def find_preset_temp(self, preset_mode):
|
||||||
"""Find the right temperature of a preset considering the presence if configured"""
|
"""Find the right temperature of a preset considering the presence if configured"""
|
||||||
|
if preset_mode is None or preset_mode == "none":
|
||||||
|
return (
|
||||||
|
self._attr_max_temp
|
||||||
|
if self._ac_mode and self._hvac_mode == HVACMode.COOL
|
||||||
|
else self._attr_min_temp
|
||||||
|
)
|
||||||
|
|
||||||
if preset_mode == PRESET_SECURITY:
|
if preset_mode == PRESET_SECURITY:
|
||||||
return (
|
return (
|
||||||
self._target_temp
|
self._target_temp
|
||||||
) # in security just keep the current target temperature, the thermostat should be off
|
) # in security just keep the current target temperature, the thermostat should be off
|
||||||
if preset_mode == PRESET_POWER:
|
if preset_mode == PRESET_POWER:
|
||||||
return self._power_temp
|
return self._power_temp
|
||||||
|
if preset_mode == PRESET_ACTIVITY:
|
||||||
|
return self._presets[
|
||||||
|
self._motion_preset
|
||||||
|
if self._motion_state == STATE_ON
|
||||||
|
else self._no_motion_preset
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
# Select _ac presets if in COOL Mode (or over_switch with _ac_mode)
|
# Select _ac presets if in COOL Mode (or over_switch with _ac_mode)
|
||||||
if self._ac_mode and self._hvac_mode == HVACMode.COOL:
|
if self._ac_mode and self._hvac_mode == HVACMode.COOL:
|
||||||
@@ -1209,7 +1222,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
|
|
||||||
_LOGGER.info("%s - find preset temp: %s", self, preset_mode)
|
_LOGGER.info("%s - find preset temp: %s", self, preset_mode)
|
||||||
|
|
||||||
if self._presence_on is False or self._presence_state in [
|
if not self._presence_on or self._presence_state in [
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
STATE_HOME,
|
STATE_HOME,
|
||||||
]:
|
]:
|
||||||
@@ -1508,17 +1521,17 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
raise ValueError(f"Sensor has illegal state {state.state}")
|
raise ValueError(f"Sensor has illegal state {state.state}")
|
||||||
self._cur_temp = cur_temp
|
self._cur_temp = cur_temp
|
||||||
|
|
||||||
self._last_temperature_mesure = self.get_state_date_or_now(state)
|
self._last_temperature_measure = self.get_state_date_or_now(state)
|
||||||
|
|
||||||
# calculate the smooth_temperature with EMA calculation
|
# calculate the smooth_temperature with EMA calculation
|
||||||
self._ema_temp = self._ema_algo.calculate_ema(
|
self._ema_temp = self._ema_algo.calculate_ema(
|
||||||
self._cur_temp, self._last_temperature_mesure
|
self._cur_temp, self._last_temperature_measure
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"%s - After setting _last_temperature_mesure %s , state.last_changed.replace=%s",
|
"%s - After setting _last_temperature_measure %s , state.last_changed.replace=%s",
|
||||||
self,
|
self,
|
||||||
self._last_temperature_mesure,
|
self._last_temperature_measure,
|
||||||
state.last_changed.astimezone(self._current_tz),
|
state.last_changed.astimezone(self._current_tz),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1540,12 +1553,12 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
if math.isnan(cur_ext_temp) or math.isinf(cur_ext_temp):
|
if math.isnan(cur_ext_temp) or math.isinf(cur_ext_temp):
|
||||||
raise ValueError(f"Sensor has illegal state {state.state}")
|
raise ValueError(f"Sensor has illegal state {state.state}")
|
||||||
self._cur_ext_temp = cur_ext_temp
|
self._cur_ext_temp = cur_ext_temp
|
||||||
self._last_ext_temperature_mesure = self.get_state_date_or_now(state)
|
self._last_ext_temperature_measure = self.get_state_date_or_now(state)
|
||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"%s - After setting _last_ext_temperature_mesure %s , state.last_changed.replace=%s",
|
"%s - After setting _last_ext_temperature_measure %s , state.last_changed.replace=%s",
|
||||||
self,
|
self,
|
||||||
self._last_ext_temperature_mesure,
|
self._last_ext_temperature_measure,
|
||||||
state.last_changed.astimezone(self._current_tz),
|
state.last_changed.astimezone(self._current_tz),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1725,7 +1738,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
else:
|
else:
|
||||||
slope = self._window_auto_algo.add_temp_measurement(
|
slope = self._window_auto_algo.add_temp_measurement(
|
||||||
temperature=self._ema_temp,
|
temperature=self._ema_temp,
|
||||||
datetime_measure=self._last_temperature_mesure,
|
datetime_measure=self._last_temperature_measure,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
@@ -1929,10 +1942,10 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
"""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_mesure.replace(tzinfo=self._current_tz)
|
now - self._last_temperature_measure.replace(tzinfo=self._current_tz)
|
||||||
).total_seconds() / 60.0
|
).total_seconds() / 60.0
|
||||||
delta_ext_temp = (
|
delta_ext_temp = (
|
||||||
now - self._last_ext_temperature_mesure.replace(tzinfo=self._current_tz)
|
now - self._last_ext_temperature_measure.replace(tzinfo=self._current_tz)
|
||||||
).total_seconds() / 60.0
|
).total_seconds() / 60.0
|
||||||
|
|
||||||
mode_cond = self._hvac_mode != HVACMode.OFF
|
mode_cond = self._hvac_mode != HVACMode.OFF
|
||||||
@@ -2003,10 +2016,10 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self.send_event(
|
self.send_event(
|
||||||
EventType.TEMPERATURE_EVENT,
|
EventType.TEMPERATURE_EVENT,
|
||||||
{
|
{
|
||||||
"last_temperature_mesure": self._last_temperature_mesure.replace(
|
"last_temperature_measure": self._last_temperature_measure.replace(
|
||||||
tzinfo=self._current_tz
|
tzinfo=self._current_tz
|
||||||
).isoformat(),
|
).isoformat(),
|
||||||
"last_ext_temperature_mesure": self._last_ext_temperature_mesure.replace(
|
"last_ext_temperature_measure": self._last_ext_temperature_measure.replace(
|
||||||
tzinfo=self._current_tz
|
tzinfo=self._current_tz
|
||||||
).isoformat(),
|
).isoformat(),
|
||||||
"current_temp": self._cur_temp,
|
"current_temp": self._cur_temp,
|
||||||
@@ -2031,10 +2044,10 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
EventType.SECURITY_EVENT,
|
EventType.SECURITY_EVENT,
|
||||||
{
|
{
|
||||||
"type": "start",
|
"type": "start",
|
||||||
"last_temperature_mesure": self._last_temperature_mesure.replace(
|
"last_temperature_measure": self._last_temperature_measure.replace(
|
||||||
tzinfo=self._current_tz
|
tzinfo=self._current_tz
|
||||||
).isoformat(),
|
).isoformat(),
|
||||||
"last_ext_temperature_mesure": self._last_ext_temperature_mesure.replace(
|
"last_ext_temperature_measure": self._last_ext_temperature_measure.replace(
|
||||||
tzinfo=self._current_tz
|
tzinfo=self._current_tz
|
||||||
).isoformat(),
|
).isoformat(),
|
||||||
"current_temp": self._cur_temp,
|
"current_temp": self._cur_temp,
|
||||||
@@ -2062,10 +2075,10 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
EventType.SECURITY_EVENT,
|
EventType.SECURITY_EVENT,
|
||||||
{
|
{
|
||||||
"type": "end",
|
"type": "end",
|
||||||
"last_temperature_mesure": self._last_temperature_mesure.replace(
|
"last_temperature_measure": self._last_temperature_measure.replace(
|
||||||
tzinfo=self._current_tz
|
tzinfo=self._current_tz
|
||||||
).isoformat(),
|
).isoformat(),
|
||||||
"last_ext_temperature_mesure": self._last_ext_temperature_mesure.replace(
|
"last_ext_temperature_measure": self._last_ext_temperature_measure.replace(
|
||||||
tzinfo=self._current_tz
|
tzinfo=self._current_tz
|
||||||
).isoformat(),
|
).isoformat(),
|
||||||
"current_temp": self._cur_temp,
|
"current_temp": self._cur_temp,
|
||||||
@@ -2190,10 +2203,10 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
"security_delay_min": self._security_delay_min,
|
"security_delay_min": self._security_delay_min,
|
||||||
"security_min_on_percent": self._security_min_on_percent,
|
"security_min_on_percent": self._security_min_on_percent,
|
||||||
"security_default_on_percent": self._security_default_on_percent,
|
"security_default_on_percent": self._security_default_on_percent,
|
||||||
"last_temperature_datetime": self._last_temperature_mesure.astimezone(
|
"last_temperature_datetime": self._last_temperature_measure.astimezone(
|
||||||
self._current_tz
|
self._current_tz
|
||||||
).isoformat(),
|
).isoformat(),
|
||||||
"last_ext_temperature_datetime": self._last_ext_temperature_mesure.astimezone(
|
"last_ext_temperature_datetime": self._last_ext_temperature_measure.astimezone(
|
||||||
self._current_tz
|
self._current_tz
|
||||||
).isoformat(),
|
).isoformat(),
|
||||||
"security_state": self._security_state,
|
"security_state": self._security_state,
|
||||||
@@ -2207,6 +2220,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
"timezone": str(self._current_tz),
|
"timezone": str(self._current_tz),
|
||||||
"window_sensor_entity_id": self._window_sensor_entity_id,
|
"window_sensor_entity_id": self._window_sensor_entity_id,
|
||||||
"window_delay_sec": self._window_delay_sec,
|
"window_delay_sec": self._window_delay_sec,
|
||||||
|
"window_auto_enabled": self.is_window_auto_enabled,
|
||||||
"window_auto_open_threshold": self._window_auto_open_threshold,
|
"window_auto_open_threshold": self._window_auto_open_threshold,
|
||||||
"window_auto_close_threshold": self._window_auto_close_threshold,
|
"window_auto_close_threshold": self._window_auto_close_threshold,
|
||||||
"window_auto_max_duration": self._window_auto_max_duration,
|
"window_auto_max_duration": self._window_auto_max_duration,
|
||||||
|
|||||||
@@ -265,12 +265,16 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
CONF_AUTO_REGULATION_MODE, default=CONF_AUTO_REGULATION_NONE
|
CONF_AUTO_REGULATION_MODE, default=CONF_AUTO_REGULATION_NONE
|
||||||
): selector.SelectSelector(
|
): selector.SelectSelector(
|
||||||
selector.SelectSelectorConfig(
|
selector.SelectSelectorConfig(
|
||||||
options=CONF_AUTO_REGULATION_MODES, translation_key="auto_regulation_mode"
|
options=CONF_AUTO_REGULATION_MODES,
|
||||||
|
translation_key="auto_regulation_mode",
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
vol.Optional(CONF_AUTO_REGULATION_DTEMP, default=0.5): vol.Coerce(float),
|
vol.Optional(CONF_AUTO_REGULATION_DTEMP, default=0.5): vol.Coerce(
|
||||||
vol.Optional(CONF_AUTO_REGULATION_PERIOD_MIN, default=5): cv.positive_int
|
float
|
||||||
|
),
|
||||||
|
vol.Optional(
|
||||||
|
CONF_AUTO_REGULATION_PERIOD_MIN, default=5
|
||||||
|
): cv.positive_int,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -862,6 +866,9 @@ class VersatileThermostatOptionsFlowHandler(
|
|||||||
"""Finalization of the ConfigEntry creation"""
|
"""Finalization of the ConfigEntry creation"""
|
||||||
if not self._infos[CONF_USE_WINDOW_FEATURE]:
|
if not self._infos[CONF_USE_WINDOW_FEATURE]:
|
||||||
self._infos[CONF_WINDOW_SENSOR] = None
|
self._infos[CONF_WINDOW_SENSOR] = None
|
||||||
|
self._infos[CONF_WINDOW_AUTO_CLOSE_THRESHOLD] = None
|
||||||
|
self._infos[CONF_WINDOW_AUTO_OPEN_THRESHOLD] = None
|
||||||
|
self._infos[CONF_WINDOW_AUTO_MAX_DURATION] = None
|
||||||
if not self._infos[CONF_USE_MOTION_FEATURE]:
|
if not self._infos[CONF_USE_MOTION_FEATURE]:
|
||||||
self._infos[CONF_MOTION_SENSOR] = None
|
self._infos[CONF_MOTION_SENSOR] = None
|
||||||
if not self._infos[CONF_USE_POWER_FEATURE]:
|
if not self._infos[CONF_USE_POWER_FEATURE]:
|
||||||
|
|||||||
@@ -396,7 +396,7 @@ class LastTemperatureSensor(VersatileThermostatBaseEntity, SensorEntity):
|
|||||||
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
|
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
|
||||||
|
|
||||||
old_state = self._attr_native_value
|
old_state = self._attr_native_value
|
||||||
self._attr_native_value = self.my_climate.last_temperature_mesure
|
self._attr_native_value = self.my_climate.last_temperature_measure
|
||||||
if old_state != self._attr_native_value:
|
if old_state != self._attr_native_value:
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
return
|
return
|
||||||
@@ -425,7 +425,7 @@ class LastExtTemperatureSensor(VersatileThermostatBaseEntity, SensorEntity):
|
|||||||
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
|
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
|
||||||
|
|
||||||
old_state = self._attr_native_value
|
old_state = self._attr_native_value
|
||||||
self._attr_native_value = self.my_climate.last_ext_temperature_mesure
|
self._attr_native_value = self.my_climate.last_ext_temperature_measure
|
||||||
if old_state != self._attr_native_value:
|
if old_state != self._attr_native_value:
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
return
|
return
|
||||||
|
|||||||
+36
-12
@@ -7,6 +7,7 @@ from datetime import datetime, timedelta
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .commons import *
|
from .commons import *
|
||||||
|
|
||||||
logging.getLogger().setLevel(logging.DEBUG)
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
@@ -362,10 +363,12 @@ async def test_bug_82(
|
|||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
title="TheOverClimateMockName",
|
title="TheOverClimateMockName",
|
||||||
unique_id="uniqueId",
|
unique_id="uniqueId",
|
||||||
data=PARTIAL_CLIMATE_CONFIG, # 5 minutes security delay
|
data=PARTIAL_CLIMATE_CONFIG, # 5 minutes security delay
|
||||||
)
|
)
|
||||||
|
|
||||||
fake_underlying_climate = MockUnavailableClimate(hass, "mockUniqueId", "MockClimateName", {})
|
fake_underlying_climate = MockUnavailableClimate(
|
||||||
|
hass, "mockUniqueId", "MockClimateName", {}
|
||||||
|
)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||||
@@ -420,11 +423,13 @@ async def test_bug_82(
|
|||||||
mock_find_climate.assert_has_calls([call.find_underlying_entity()])
|
mock_find_climate.assert_has_calls([call.find_underlying_entity()])
|
||||||
|
|
||||||
# Force security mode
|
# Force security mode
|
||||||
assert entity._last_ext_temperature_mesure is not None
|
assert entity._last_ext_temperature_measure is not None
|
||||||
assert entity._last_temperature_mesure is not None
|
assert entity._last_temperature_measure is not None
|
||||||
assert (entity._last_temperature_mesure.astimezone(tz) - now).total_seconds() < 1
|
|
||||||
assert (
|
assert (
|
||||||
entity._last_ext_temperature_mesure.astimezone(tz) - now
|
entity._last_temperature_measure.astimezone(tz) - now
|
||||||
|
).total_seconds() < 1
|
||||||
|
assert (
|
||||||
|
entity._last_ext_temperature_measure.astimezone(tz) - now
|
||||||
).total_seconds() < 1
|
).total_seconds() < 1
|
||||||
|
|
||||||
# Tries to turns on the Thermostat
|
# Tries to turns on the Thermostat
|
||||||
@@ -443,8 +448,9 @@ async def test_bug_82(
|
|||||||
await send_temperature_change_event(entity, 15, event_timestamp)
|
await send_temperature_change_event(entity, 15, event_timestamp)
|
||||||
# Should stay False
|
# Should stay False
|
||||||
assert entity.security_state is False
|
assert entity.security_state is False
|
||||||
assert entity.preset_mode == 'none'
|
assert entity.preset_mode == "none"
|
||||||
assert entity._saved_preset_mode == 'none'
|
assert entity._saved_preset_mode == "none"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||||
@@ -463,11 +469,13 @@ async def test_bug_101(
|
|||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
title="TheOverClimateMockName",
|
title="TheOverClimateMockName",
|
||||||
unique_id="uniqueId",
|
unique_id="uniqueId",
|
||||||
data=PARTIAL_CLIMATE_NOT_REGULATED_CONFIG, # 5 minutes security delay
|
data=PARTIAL_CLIMATE_NOT_REGULATED_CONFIG, # 5 minutes security delay
|
||||||
)
|
)
|
||||||
|
|
||||||
# Underlying is in HEAT mode but should be shutdown at startup
|
# Underlying is in HEAT mode but should be shutdown at startup
|
||||||
fake_underlying_climate = MockClimate(hass, "mockUniqueId", "MockClimateName", {}, HVACMode.HEAT, HVACAction.HEATING)
|
fake_underlying_climate = MockClimate(
|
||||||
|
hass, "mockUniqueId", "MockClimateName", {}, HVACMode.HEAT, HVACAction.HEATING
|
||||||
|
)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||||
@@ -531,7 +539,15 @@ async def test_bug_101(
|
|||||||
assert entity.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 at now -> the event will be disgarded because to fast (to avoid loop cf issue 121)
|
||||||
await send_climate_change_event_with_temperature(entity, HVACMode.HEAT, HVACMode.HEAT, HVACAction.OFF, HVACAction.OFF, now, 12.75)
|
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
|
# Should NOT have been switched to Manual preset
|
||||||
assert entity.target_temperature == 17
|
assert entity.target_temperature == 17
|
||||||
assert entity.preset_mode is PRESET_COMFORT
|
assert entity.preset_mode is PRESET_COMFORT
|
||||||
@@ -540,6 +556,14 @@ async def test_bug_101(
|
|||||||
# Wait 11 sec
|
# Wait 11 sec
|
||||||
event_timestamp = now + timedelta(seconds=11)
|
event_timestamp = now + timedelta(seconds=11)
|
||||||
assert entity.is_regulated is False
|
assert entity.is_regulated is False
|
||||||
await send_climate_change_event_with_temperature(entity, HVACMode.HEAT, HVACMode.HEAT, HVACAction.OFF, HVACAction.OFF, event_timestamp, 12.75)
|
await send_climate_change_event_with_temperature(
|
||||||
|
entity,
|
||||||
|
HVACMode.HEAT,
|
||||||
|
HVACMode.HEAT,
|
||||||
|
HVACAction.OFF,
|
||||||
|
HVACAction.OFF,
|
||||||
|
event_timestamp,
|
||||||
|
12.75,
|
||||||
|
)
|
||||||
assert entity.target_temperature == 12.75
|
assert entity.target_temperature == 12.75
|
||||||
assert entity.preset_mode is PRESET_NONE
|
assert entity.preset_mode is PRESET_NONE
|
||||||
|
|||||||
+24
-24
@@ -76,11 +76,11 @@ async def test_security_feature(hass: HomeAssistant, skip_hass_states_is_state):
|
|||||||
PRESET_COMFORT,
|
PRESET_COMFORT,
|
||||||
PRESET_BOOST,
|
PRESET_BOOST,
|
||||||
]
|
]
|
||||||
assert entity._last_ext_temperature_mesure is not None
|
assert entity._last_ext_temperature_measure is not None
|
||||||
assert entity._last_temperature_mesure is not None
|
assert entity._last_temperature_measure is not None
|
||||||
assert (entity._last_temperature_mesure.astimezone(tz) - now).total_seconds() < 1
|
assert (entity._last_temperature_measure.astimezone(tz) - now).total_seconds() < 1
|
||||||
assert (
|
assert (
|
||||||
entity._last_ext_temperature_mesure.astimezone(tz) - now
|
entity._last_ext_temperature_measure.astimezone(tz) - now
|
||||||
).total_seconds() < 1
|
).total_seconds() < 1
|
||||||
|
|
||||||
# set a preset
|
# set a preset
|
||||||
@@ -116,8 +116,8 @@ async def test_security_feature(hass: HomeAssistant, skip_hass_states_is_state):
|
|||||||
call.send_event(
|
call.send_event(
|
||||||
EventType.TEMPERATURE_EVENT,
|
EventType.TEMPERATURE_EVENT,
|
||||||
{
|
{
|
||||||
"last_temperature_mesure": event_timestamp.isoformat(),
|
"last_temperature_measure": event_timestamp.isoformat(),
|
||||||
"last_ext_temperature_mesure": entity._last_ext_temperature_mesure.isoformat(),
|
"last_ext_temperature_measure": entity._last_ext_temperature_measure.isoformat(),
|
||||||
"current_temp": 15,
|
"current_temp": 15,
|
||||||
"current_ext_temp": None,
|
"current_ext_temp": None,
|
||||||
"target_temp": 18,
|
"target_temp": 18,
|
||||||
@@ -127,8 +127,8 @@ async def test_security_feature(hass: HomeAssistant, skip_hass_states_is_state):
|
|||||||
EventType.SECURITY_EVENT,
|
EventType.SECURITY_EVENT,
|
||||||
{
|
{
|
||||||
"type": "start",
|
"type": "start",
|
||||||
"last_temperature_mesure": event_timestamp.isoformat(),
|
"last_temperature_measure": event_timestamp.isoformat(),
|
||||||
"last_ext_temperature_mesure": entity._last_ext_temperature_mesure.isoformat(),
|
"last_ext_temperature_measure": entity._last_ext_temperature_measure.isoformat(),
|
||||||
"current_temp": 15,
|
"current_temp": 15,
|
||||||
"current_ext_temp": None,
|
"current_ext_temp": None,
|
||||||
"target_temp": 18,
|
"target_temp": 18,
|
||||||
@@ -180,10 +180,10 @@ async def test_security_feature(hass: HomeAssistant, skip_hass_states_is_state):
|
|||||||
EventType.SECURITY_EVENT,
|
EventType.SECURITY_EVENT,
|
||||||
{
|
{
|
||||||
"type": "end",
|
"type": "end",
|
||||||
"last_temperature_mesure": event_timestamp.astimezone(
|
"last_temperature_measure": event_timestamp.astimezone(
|
||||||
tz
|
tz
|
||||||
).isoformat(),
|
).isoformat(),
|
||||||
"last_ext_temperature_mesure": entity._last_ext_temperature_mesure.astimezone(
|
"last_ext_temperature_measure": entity._last_ext_temperature_measure.astimezone(
|
||||||
tz
|
tz
|
||||||
).isoformat(),
|
).isoformat(),
|
||||||
"current_temp": 15.2,
|
"current_temp": 15.2,
|
||||||
@@ -253,11 +253,11 @@ async def test_security_feature_back_on_percent(
|
|||||||
|
|
||||||
assert entity._security_state is False
|
assert entity._security_state is False
|
||||||
assert entity.preset_mode is not PRESET_SECURITY
|
assert entity.preset_mode is not PRESET_SECURITY
|
||||||
assert entity._last_ext_temperature_mesure is not None
|
assert entity._last_ext_temperature_measure is not None
|
||||||
assert entity._last_temperature_mesure is not None
|
assert entity._last_temperature_measure is not None
|
||||||
assert (entity._last_temperature_mesure.astimezone(tz) - now).total_seconds() < 1
|
assert (entity._last_temperature_measure.astimezone(tz) - now).total_seconds() < 1
|
||||||
assert (
|
assert (
|
||||||
entity._last_ext_temperature_mesure.astimezone(tz) - now
|
entity._last_ext_temperature_measure.astimezone(tz) - now
|
||||||
).total_seconds() < 1
|
).total_seconds() < 1
|
||||||
|
|
||||||
# set a preset
|
# set a preset
|
||||||
@@ -311,8 +311,8 @@ async def test_security_feature_back_on_percent(
|
|||||||
call.send_event(
|
call.send_event(
|
||||||
EventType.TEMPERATURE_EVENT,
|
EventType.TEMPERATURE_EVENT,
|
||||||
{
|
{
|
||||||
"last_temperature_mesure": event_timestamp.isoformat(),
|
"last_temperature_measure": event_timestamp.isoformat(),
|
||||||
"last_ext_temperature_mesure": entity._last_ext_temperature_mesure.isoformat(),
|
"last_ext_temperature_measure": entity._last_ext_temperature_measure.isoformat(),
|
||||||
"current_temp": 17,
|
"current_temp": 17,
|
||||||
"current_ext_temp": None,
|
"current_ext_temp": None,
|
||||||
"target_temp": 19,
|
"target_temp": 19,
|
||||||
@@ -322,8 +322,8 @@ async def test_security_feature_back_on_percent(
|
|||||||
EventType.SECURITY_EVENT,
|
EventType.SECURITY_EVENT,
|
||||||
{
|
{
|
||||||
"type": "start",
|
"type": "start",
|
||||||
"last_temperature_mesure": event_timestamp.isoformat(),
|
"last_temperature_measure": event_timestamp.isoformat(),
|
||||||
"last_ext_temperature_mesure": entity._last_ext_temperature_mesure.isoformat(),
|
"last_ext_temperature_measure": entity._last_ext_temperature_measure.isoformat(),
|
||||||
"current_temp": 17,
|
"current_temp": 17,
|
||||||
"current_ext_temp": None,
|
"current_ext_temp": None,
|
||||||
"target_temp": 19,
|
"target_temp": 19,
|
||||||
@@ -371,10 +371,10 @@ async def test_security_feature_back_on_percent(
|
|||||||
EventType.SECURITY_EVENT,
|
EventType.SECURITY_EVENT,
|
||||||
{
|
{
|
||||||
"type": "end",
|
"type": "end",
|
||||||
"last_temperature_mesure": event_timestamp.astimezone(
|
"last_temperature_measure": event_timestamp.astimezone(
|
||||||
tz
|
tz
|
||||||
).isoformat(),
|
).isoformat(),
|
||||||
"last_ext_temperature_mesure": entity._last_ext_temperature_mesure.astimezone(
|
"last_ext_temperature_measure": entity._last_ext_temperature_measure.astimezone(
|
||||||
tz
|
tz
|
||||||
).isoformat(),
|
).isoformat(),
|
||||||
"current_temp": 18.92,
|
"current_temp": 18.92,
|
||||||
@@ -469,13 +469,13 @@ async def test_security_over_climate(
|
|||||||
mock_find_climate.assert_has_calls([call.find_underlying_entity()])
|
mock_find_climate.assert_has_calls([call.find_underlying_entity()])
|
||||||
|
|
||||||
# Force security mode
|
# Force security mode
|
||||||
assert entity._last_ext_temperature_mesure is not None
|
assert entity._last_ext_temperature_measure is not None
|
||||||
assert entity._last_temperature_mesure is not None
|
assert entity._last_temperature_measure is not None
|
||||||
assert (
|
assert (
|
||||||
entity._last_temperature_mesure.astimezone(tz) - now
|
entity._last_temperature_measure.astimezone(tz) - now
|
||||||
).total_seconds() < 1
|
).total_seconds() < 1
|
||||||
assert (
|
assert (
|
||||||
entity._last_ext_temperature_mesure.astimezone(tz) - now
|
entity._last_ext_temperature_measure.astimezone(tz) - now
|
||||||
).total_seconds() < 1
|
).total_seconds() < 1
|
||||||
|
|
||||||
# Tries to turns on the Thermostat
|
# Tries to turns on the Thermostat
|
||||||
|
|||||||
+66
-2
@@ -13,8 +13,12 @@ from homeassistant.components.climate import ClimateEntity, DOMAIN as CLIMATE_DO
|
|||||||
from pytest_homeassistant_custom_component.common import MockConfigEntry
|
from pytest_homeassistant_custom_component.common import MockConfigEntry
|
||||||
|
|
||||||
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
|
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
|
||||||
from custom_components.versatile_thermostat.thermostat_climate import ThermostatOverClimate
|
from custom_components.versatile_thermostat.thermostat_climate import (
|
||||||
from custom_components.versatile_thermostat.thermostat_switch import ThermostatOverSwitch
|
ThermostatOverClimate,
|
||||||
|
)
|
||||||
|
from custom_components.versatile_thermostat.thermostat_switch import (
|
||||||
|
ThermostatOverSwitch,
|
||||||
|
)
|
||||||
|
|
||||||
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
|
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||||
|
|
||||||
@@ -224,3 +228,63 @@ async def test_over_4switch_full_start(hass: HomeAssistant, skip_hass_states_is_
|
|||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||||
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||||
|
async def test_over_switch_deactivate_preset(
|
||||||
|
hass: HomeAssistant, skip_hass_states_is_state
|
||||||
|
):
|
||||||
|
"""Test the normal full start of a thermostat in thermostat_over_switch type"""
|
||||||
|
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
title="TheOverSwitchMockName",
|
||||||
|
unique_id="uniqueId",
|
||||||
|
data={
|
||||||
|
CONF_NAME: "TheOverSwitchMockName",
|
||||||
|
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
|
||||||
|
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
||||||
|
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
||||||
|
CONF_CYCLE_MIN: 8,
|
||||||
|
CONF_TEMP_MIN: 15,
|
||||||
|
CONF_TEMP_MAX: 30,
|
||||||
|
"eco_temp": 17,
|
||||||
|
"comfort_temp": 0,
|
||||||
|
"boost_temp": 19,
|
||||||
|
CONF_USE_WINDOW_FEATURE: False,
|
||||||
|
CONF_USE_MOTION_FEATURE: False,
|
||||||
|
CONF_USE_POWER_FEATURE: False,
|
||||||
|
CONF_USE_PRESENCE_FEATURE: False,
|
||||||
|
CONF_HEATER: "switch.mock_switch1",
|
||||||
|
CONF_HEATER_2: None,
|
||||||
|
CONF_HEATER_3: None,
|
||||||
|
CONF_HEATER_4: None,
|
||||||
|
CONF_SECURITY_DELAY_MIN: 10,
|
||||||
|
CONF_MINIMAL_ACTIVATION_DELAY: 10,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
entity: BaseThermostat = await create_thermostat(
|
||||||
|
hass, entry, "climate.theoverswitchmockname"
|
||||||
|
)
|
||||||
|
assert entity
|
||||||
|
assert isinstance(entity, ThermostatOverSwitch)
|
||||||
|
|
||||||
|
assert entity.preset_modes == [
|
||||||
|
PRESET_NONE,
|
||||||
|
PRESET_ECO,
|
||||||
|
# PRESET_COMFORT,
|
||||||
|
PRESET_BOOST,
|
||||||
|
]
|
||||||
|
assert entity.preset_mode is PRESET_NONE
|
||||||
|
|
||||||
|
# try to set the COMFORT Preset which is absent
|
||||||
|
try:
|
||||||
|
await entity.async_set_preset_mode(PRESET_COMFORT)
|
||||||
|
except ValueError as err:
|
||||||
|
print(err)
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
finally:
|
||||||
|
assert entity.preset_mode is PRESET_NONE
|
||||||
|
|||||||
Reference in New Issue
Block a user