Compare commits

..

2 Commits

Author SHA1 Message Date
Jean-Marc Collin
6e40a15262 Documentation for release 2023-03-05 10:36:58 +01:00
Jean-Marc Collin
974e5d26db Finish unit tests #48
Remove warnings #48
Add theme colors #27
2023-03-05 10:11:16 +01:00
19 changed files with 1181 additions and 94 deletions

View File

@@ -169,3 +169,12 @@ switch:
option: comfort-2 option: comfort-2
target: target:
entity_id: select.seche_serviettes_sdb_rdc_cable_outlet_mode entity_id: select.seche_serviettes_sdb_rdc_cable_outlet_mode
frontend:
themes:
versatile_thermostat_theme:
state-binary_sensor-safety-on-color: "#FF0B0B"
state-binary_sensor-power-on-color: "#FF0B0B"
state-binary_sensor-window-on-color: "rgb(156, 39, 176)"
state-binary_sensor-motion-on-color: "rgb(156, 39, 176)"
state-binary_sensor-presence-on-color: "lightgreen"

View File

@@ -45,8 +45,6 @@
- [Et toujours de mieux en mieux avec l'AappDaemon NOTIFIER pour notifier les évènements](#et-toujours-de-mieux-en-mieux-avec-laappdaemon-notifier-pour-notifier-les-évènements) - [Et toujours de mieux en mieux avec l'AappDaemon NOTIFIER pour notifier les évènements](#et-toujours-de-mieux-en-mieux-avec-laappdaemon-notifier-pour-notifier-les-évènements)
- [Les contributions sont les bienvenues !](#les-contributions-sont-les-bienvenues) - [Les contributions sont les bienvenues !](#les-contributions-sont-les-bienvenues)
_Composant développé à l'aide de l'incroyable modèle de développement [[blueprint](https://github.com/custom-components/integration_blueprint)]._
Ce composant personnalisé pour Home Assistant est une mise à niveau et est une réécriture complète du composant "Awesome thermostat" (voir [Github](https://github.com/dadge/awesome_thermostat)) avec l'ajout de fonctionnalités. Ce composant personnalisé pour Home Assistant est une mise à niveau et est une réécriture complète du composant "Awesome thermostat" (voir [Github](https://github.com/dadge/awesome_thermostat)) avec l'ajout de fonctionnalités.
# Quand l'utiliser et ne pas l'utiliser # Quand l'utiliser et ne pas l'utiliser
@@ -354,6 +352,22 @@ Dans l'ordre, il y a :
12. l'état de l'ouverture (si la gestion des ouvertures est configurée), 12. l'état de l'ouverture (si la gestion des ouvertures est configurée),
13. l'état du mouvement (si la gestion du mouvements est configurée) 13. l'état du mouvement (si la gestion du mouvements est configurée)
Pour colorer les capteurs, ajouter ces lignes et personnalisez les au besoin, dans votre configuration.yaml :
```
frontend:
themes:
versatile_thermostat_theme:
state-binary_sensor-safety-on-color: "#FF0B0B"
state-binary_sensor-power-on-color: "#FF0B0B"
state-binary_sensor-window-on-color: "rgb(156, 39, 176)"
state-binary_sensor-motion-on-color: "rgb(156, 39, 176)"
state-binary_sensor-presence-on-color: "lightgreen"
```
et choisissez le thème ```versatile_thermostat_theme``` dans la configuration du panel. Vous obtiendrez quelque-chose qui va ressembler à ça :
![image](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/colored-thermostat-sensors.png?raw=true)
# Services # Services
Cette implémentation personnalisée offre des services spécifiques pour faciliter l'intégration avec d'autres composants Home Assistant. Cette implémentation personnalisée offre des services spécifiques pour faciliter l'intégration avec d'autres composants Home Assistant.

View File

@@ -45,7 +45,6 @@
- [And always better and better with the NOTIFIER daemon app to notify events](#and-always-better-and-better-with-the-notifier-daemon-app-to-notify-events) - [And always better and better with the NOTIFIER daemon app to notify events](#and-always-better-and-better-with-the-notifier-daemon-app-to-notify-events)
- [Contributions are welcome!](#contributions-are-welcome) - [Contributions are welcome!](#contributions-are-welcome)
_Component developed by using the amazing development template [[blueprint](https://github.com/custom-components/integration_blueprint)]._
This custom component for Home Assistant is an upgrade and is a complete rewrite of the component "Awesome thermostat" (see [Github](https://github.com/dadge/awesome_thermostat)) with addition of features. This custom component for Home Assistant is an upgrade and is a complete rewrite of the component "Awesome thermostat" (see [Github](https://github.com/dadge/awesome_thermostat)) with addition of features.
@@ -340,6 +339,22 @@ In order, there are:
12. opening status (if opening management is configured), 12. opening status (if opening management is configured),
13. motion status (if motion management is configured) 13. motion status (if motion management is configured)
To color the sensors, add these lines and customize them as needed, in your configuration.yaml:
```
frontend:
themes:
versatile_thermostat_theme:
state-binary_sensor-safety-on-color: "#FF0B0B"
state-binary_sensor-power-on-color: "#FF0B0B"
state-binary_sensor-window-on-color: "rgb(156, 39, 176)"
state-binary_sensor-motion-on-color: "rgb(156, 39, 176)"
state-binary_sensor-presence-on-color: "lightgreen"
```
and choose the ```versatile_thermostat_theme``` theme in the panel configuration. You will get something that will look like this:
![image](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/colored-thermostat-sensors.png?raw=true)
# Services # Services
This custom implementation offers some specific services to facilitate integration with others Home Assisstant components. This custom implementation offers some specific services to facilitate integration with others Home Assisstant components.

View File

@@ -99,7 +99,7 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry):
if config_entry.version == 1: if config_entry.version == 1:
new = {**config_entry.data} new = {**config_entry.data}
# TODO: modify Config Entry data # TO DO: modify Config Entry data if there will be something to migrate
config_entry.version = 2 config_entry.version = 2
hass.config_entries.async_update_entry(config_entry, data=new) hass.config_entries.async_update_entry(config_entry, data=new)

View File

@@ -5,7 +5,10 @@ from homeassistant.core import HomeAssistant, callback, Event
from homeassistant.const import STATE_ON from homeassistant.const import STATE_ON
from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorDeviceClass,
)
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
@@ -56,17 +59,25 @@ class SecurityBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME)) super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
self._attr_name = "Security state" self._attr_name = "Security state"
self._attr_unique_id = f"{self._device_name}_security_state" self._attr_unique_id = f"{self._device_name}_security_state"
self._attr_is_on = False
@callback @callback
async def async_my_climate_changed(self, event: Event): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", event.origin.name) _LOGGER.debug(
"%s - climate state change",
event.origin.name if event and event.origin else None,
)
old_state = self._attr_is_on old_state = self._attr_is_on
self._attr_is_on = self.my_climate.security_state self._attr_is_on = self.my_climate.security_state is True
if old_state != self._attr_is_on: if old_state != self._attr_is_on:
self.async_write_ha_state() self.async_write_ha_state()
return return
@property
def device_class(self) -> BinarySensorDeviceClass | None:
return BinarySensorDeviceClass.SAFETY
@property @property
def icon(self) -> str | None: def icon(self) -> str | None:
if self._attr_is_on: if self._attr_is_on:
@@ -83,17 +94,25 @@ class OverpoweringBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME)) super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
self._attr_name = "Overpowering state" self._attr_name = "Overpowering state"
self._attr_unique_id = f"{self._device_name}_overpowering_state" self._attr_unique_id = f"{self._device_name}_overpowering_state"
self._attr_is_on = False
@callback @callback
async def async_my_climate_changed(self, event: Event): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", event.origin.name) _LOGGER.debug(
"%s - climate state change",
event.origin.name if event and event.origin else None,
)
old_state = self._attr_is_on old_state = self._attr_is_on
self._attr_is_on = self.my_climate.overpowering_state self._attr_is_on = self.my_climate.overpowering_state is True
if old_state != self._attr_is_on: if old_state != self._attr_is_on:
self.async_write_ha_state() self.async_write_ha_state()
return return
@property
def device_class(self) -> BinarySensorDeviceClass | None:
return BinarySensorDeviceClass.POWER
@property @property
def icon(self) -> str | None: def icon(self) -> str | None:
if self._attr_is_on: if self._attr_is_on:
@@ -110,17 +129,25 @@ class WindowBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME)) super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
self._attr_name = "Window state" self._attr_name = "Window state"
self._attr_unique_id = f"{self._device_name}_window_state" self._attr_unique_id = f"{self._device_name}_window_state"
self._attr_is_on = False
@callback @callback
async def async_my_climate_changed(self, event: Event): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", event.origin.name) _LOGGER.debug(
"%s - climate state change",
event.origin.name if event and event.origin else None,
)
old_state = self._attr_is_on old_state = self._attr_is_on
self._attr_is_on = self.my_climate.window_state == STATE_ON self._attr_is_on = self.my_climate.window_state == STATE_ON
if old_state != self._attr_is_on: if old_state != self._attr_is_on:
self.async_write_ha_state() self.async_write_ha_state()
return return
@property
def device_class(self) -> BinarySensorDeviceClass | None:
return BinarySensorDeviceClass.WINDOW
@property @property
def icon(self) -> str | None: def icon(self) -> str | None:
if self._attr_is_on: if self._attr_is_on:
@@ -137,17 +164,25 @@ class MotionBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME)) super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
self._attr_name = "Motion state" self._attr_name = "Motion state"
self._attr_unique_id = f"{self._device_name}_motion_state" self._attr_unique_id = f"{self._device_name}_motion_state"
self._attr_is_on = False
@callback @callback
async def async_my_climate_changed(self, event: Event): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", event.origin.name) _LOGGER.debug(
"%s - climate state change",
event.origin.name if event and event.origin else None,
)
old_state = self._attr_is_on old_state = self._attr_is_on
self._attr_is_on = self.my_climate.motion_state == STATE_ON self._attr_is_on = self.my_climate.motion_state == STATE_ON
if old_state != self._attr_is_on: if old_state != self._attr_is_on:
self.async_write_ha_state() self.async_write_ha_state()
return return
@property
def device_class(self) -> BinarySensorDeviceClass | None:
return BinarySensorDeviceClass.MOTION
@property @property
def icon(self) -> str | None: def icon(self) -> str | None:
if self._attr_is_on: if self._attr_is_on:
@@ -164,18 +199,26 @@ class PresenceBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME)) super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
self._attr_name = "Presence state" self._attr_name = "Presence state"
self._attr_unique_id = f"{self._device_name}_presence_state" self._attr_unique_id = f"{self._device_name}_presence_state"
self._attr_is_on = False
@callback @callback
async def async_my_climate_changed(self, event: Event): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", event.origin.name) _LOGGER.debug(
"%s - climate state change",
event.origin.name if event and event.origin else None,
)
old_state = self._attr_is_on old_state = self._attr_is_on
self._attr_is_on = self.my_climate.presence_state == STATE_ON self._attr_is_on = self.my_climate.presence_state == STATE_ON
if old_state != self._attr_is_on: if old_state != self._attr_is_on:
self.async_write_ha_state() self.async_write_ha_state()
return return
@property
def device_class(self) -> BinarySensorDeviceClass | None:
return BinarySensorDeviceClass.PRESENCE
@property @property
def icon(self) -> str | None: def icon(self) -> str | None:
if self._attr_is_on: if self._attr_is_on:

View File

@@ -357,7 +357,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._presence_on = self._presence_sensor_entity_id is not None self._presence_on = self._presence_sensor_entity_id is not None
# TODO if self.ac_mode: # if self.ac_mode: -> MODE_COOL should be better to use thermostat_over_climate type
# self.hvac_list = [HVAC_MODE_COOL, HVAC_MODE_OFF] # self.hvac_list = [HVAC_MODE_COOL, HVAC_MODE_OFF]
# else: # else:
self._hvac_list = [HVACMode.HEAT, HVACMode.OFF] self._hvac_list = [HVACMode.HEAT, HVACMode.OFF]
@@ -1118,6 +1118,12 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
""" """
return self._attr_preset_modes return self._attr_preset_modes
@property
def is_over_climate(self) -> bool | None:
"""return True is the thermostat is over a climate
or False is over switch"""
return self._is_over_climate
async def async_set_hvac_mode(self, hvac_mode): async def async_set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode.""" """Set new target hvac mode."""
_LOGGER.info("%s - Set hvac mode: %s", self, hvac_mode) _LOGGER.info("%s - Set hvac mode: %s", self, hvac_mode)
@@ -1488,6 +1494,9 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self.hass, timedelta(seconds=self._motion_delay_sec), try_motion_condition self.hass, timedelta(seconds=self._motion_delay_sec), try_motion_condition
) )
# For testing purpose we need to access the inner function
return try_motion_condition
@callback @callback
async def _check_switch_initial_state(self): async def _check_switch_initial_state(self):
"""Prevent the device from keep running if HVAC_MODE_OFF.""" """Prevent the device from keep running if HVAC_MODE_OFF."""
@@ -1551,7 +1560,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._hvac_mode = new_state.state self._hvac_mode = new_state.state
# Interpretation of hvac # Interpretation of hvac
HVAC_ACTION_ON = [ HVAC_ACTION_ON = [ # pylint: disable=invalid-name
HVACAction.COOLING, HVACAction.COOLING,
HVACAction.DRYING, HVACAction.DRYING,
HVACAction.FAN, HVACAction.FAN,
@@ -2126,7 +2135,10 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
if self._hvac_mode == HVACMode.HEAT and on_time_sec > 0: if self._hvac_mode == HVACMode.HEAT and on_time_sec > 0:
async def _turn_on_off_later( async def _turn_on_off_later(
on: bool, time, heater_action, next_cycle_action on: bool, # pylint: disable=invalid-name
time,
heater_action,
next_cycle_action,
): ):
if self._async_cancel_cycle: if self._async_cancel_cycle:
self._async_cancel_cycle() self._async_cancel_cycle()

View File

@@ -180,7 +180,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
is_empty or self._infos.get(CONF_PRESENCE_SENSOR) is not None is_empty or self._infos.get(CONF_PRESENCE_SENSOR) is not None
) )
self.STEP_USER_DATA_SCHEMA = vol.Schema( self.STEP_USER_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{ {
vol.Required(CONF_NAME): cv.string, vol.Required(CONF_NAME): cv.string,
vol.Required( vol.Required(
@@ -212,7 +212,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
} }
) )
self.STEP_THERMOSTAT_SWITCH = vol.Schema( self.STEP_THERMOSTAT_SWITCH = vol.Schema( # pylint: disable=invalid-name
{ {
vol.Required(CONF_HEATER): selector.EntitySelector( vol.Required(CONF_HEATER): selector.EntitySelector(
selector.EntitySelectorConfig( selector.EntitySelectorConfig(
@@ -229,7 +229,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
} }
) )
self.STEP_THERMOSTAT_CLIMATE = vol.Schema( self.STEP_THERMOSTAT_CLIMATE = vol.Schema( # pylint: disable=invalid-name
{ {
vol.Required(CONF_CLIMATE): selector.EntitySelector( vol.Required(CONF_CLIMATE): selector.EntitySelector(
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN), selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN),
@@ -237,21 +237,21 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
} }
) )
self.STEP_TPI_DATA_SCHEMA = vol.Schema( self.STEP_TPI_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{ {
vol.Required(CONF_TPI_COEF_INT, default=0.6): vol.Coerce(float), vol.Required(CONF_TPI_COEF_INT, default=0.6): vol.Coerce(float),
vol.Required(CONF_TPI_COEF_EXT, default=0.01): vol.Coerce(float), vol.Required(CONF_TPI_COEF_EXT, default=0.01): vol.Coerce(float),
} }
) )
self.STEP_PRESETS_DATA_SCHEMA = vol.Schema( self.STEP_PRESETS_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{ {
vol.Optional(v, default=0.0): vol.Coerce(float) vol.Optional(v, default=0.0): vol.Coerce(float)
for (k, v) in CONF_PRESETS.items() for (k, v) in CONF_PRESETS.items()
} }
) )
self.STEP_WINDOW_DATA_SCHEMA = vol.Schema( self.STEP_WINDOW_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{ {
vol.Optional(CONF_WINDOW_SENSOR): selector.EntitySelector( vol.Optional(CONF_WINDOW_SENSOR): selector.EntitySelector(
selector.EntitySelectorConfig( selector.EntitySelectorConfig(
@@ -262,7 +262,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
} }
) )
self.STEP_MOTION_DATA_SCHEMA = vol.Schema( self.STEP_MOTION_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{ {
vol.Optional(CONF_MOTION_SENSOR): selector.EntitySelector( vol.Optional(CONF_MOTION_SENSOR): selector.EntitySelector(
selector.EntitySelectorConfig( selector.EntitySelectorConfig(
@@ -279,7 +279,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
} }
) )
self.STEP_POWER_DATA_SCHEMA = vol.Schema( self.STEP_POWER_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{ {
vol.Optional(CONF_POWER_SENSOR): selector.EntitySelector( vol.Optional(CONF_POWER_SENSOR): selector.EntitySelector(
selector.EntitySelectorConfig( selector.EntitySelectorConfig(
@@ -295,7 +295,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
} }
) )
self.STEP_PRESENCE_DATA_SCHEMA = vol.Schema( self.STEP_PRESENCE_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{ {
vol.Optional(CONF_PRESENCE_SENSOR): selector.EntitySelector( vol.Optional(CONF_PRESENCE_SENSOR): selector.EntitySelector(
selector.EntitySelectorConfig( selector.EntitySelectorConfig(
@@ -314,7 +314,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
} }
) )
self.STEP_ADVANCED_DATA_SCHEMA = vol.Schema( self.STEP_ADVANCED_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{ {
vol.Required( vol.Required(
CONF_MINIMAL_ACTIVATION_DELAY, default=10 CONF_MINIMAL_ACTIVATION_DELAY, default=10

View File

@@ -13,13 +13,13 @@ from homeassistant.components.climate import (
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
DEVICE_MANUFACTURER = "JMCOLLIN"
DEVICE_MODEL = "Versatile Thermostat"
from .prop_algorithm import ( from .prop_algorithm import (
PROPORTIONAL_FUNCTION_TPI, PROPORTIONAL_FUNCTION_TPI,
) )
DEVICE_MANUFACTURER = "JMCOLLIN"
DEVICE_MODEL = "Versatile Thermostat"
PRESET_POWER = "power" PRESET_POWER = "power"
PRESET_SECURITY = "security" PRESET_SECURITY = "security"

View File

@@ -70,9 +70,12 @@ class EnergySensor(VersatileThermostatBaseEntity, SensorEntity):
self._attr_unique_id = f"{self._device_name}_energy" self._attr_unique_id = f"{self._device_name}_energy"
@callback @callback
async def async_my_climate_changed(self, event: Event): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", event.origin.name) _LOGGER.debug(
"%s - climate state change",
event.origin.name if event and event.origin else None,
)
if math.isnan(self.my_climate.total_energy) or math.isinf( if math.isnan(self.my_climate.total_energy) or math.isinf(
self.my_climate.total_energy self.my_climate.total_energy
@@ -125,9 +128,12 @@ class MeanPowerSensor(VersatileThermostatBaseEntity, SensorEntity):
self._attr_unique_id = f"{self._device_name}_mean_power_cycle" self._attr_unique_id = f"{self._device_name}_mean_power_cycle"
@callback @callback
async def async_my_climate_changed(self, event: Event): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", event.origin.name) _LOGGER.debug(
"%s - climate state change",
event.origin.name if event and event.origin else None,
)
if math.isnan(float(self.my_climate.mean_cycle_power)) or math.isinf( if math.isnan(float(self.my_climate.mean_cycle_power)) or math.isinf(
self.my_climate.mean_cycle_power self.my_climate.mean_cycle_power
@@ -182,9 +188,12 @@ class OnPercentSensor(VersatileThermostatBaseEntity, SensorEntity):
self._attr_unique_id = f"{self._device_name}_power_percent" self._attr_unique_id = f"{self._device_name}_power_percent"
@callback @callback
async def async_my_climate_changed(self, event: Event): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", event.origin.name) _LOGGER.debug(
"%s - climate state change",
event.origin.name if event and event.origin else None,
)
on_percent = ( on_percent = (
float(self.my_climate.proportional_algorithm.on_percent) float(self.my_climate.proportional_algorithm.on_percent)
@@ -234,9 +243,12 @@ class OnTimeSensor(VersatileThermostatBaseEntity, SensorEntity):
self._attr_unique_id = f"{self._device_name}_on_time" self._attr_unique_id = f"{self._device_name}_on_time"
@callback @callback
async def async_my_climate_changed(self, event: Event): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", event.origin.name) _LOGGER.debug(
"%s - climate state change",
event.origin.name if event and event.origin else None,
)
on_time = ( on_time = (
float(self.my_climate.proportional_algorithm.on_time_sec) float(self.my_climate.proportional_algorithm.on_time_sec)
@@ -279,9 +291,12 @@ class OffTimeSensor(VersatileThermostatBaseEntity, SensorEntity):
self._attr_unique_id = f"{self._device_name}_off_time" self._attr_unique_id = f"{self._device_name}_off_time"
@callback @callback
async def async_my_climate_changed(self, event: Event): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", event.origin.name) _LOGGER.debug(
"%s - climate state change",
event.origin.name if event and event.origin else None,
)
off_time = ( off_time = (
float(self.my_climate.proportional_algorithm.off_time_sec) float(self.my_climate.proportional_algorithm.off_time_sec)
@@ -324,9 +339,12 @@ class LastTemperatureSensor(VersatileThermostatBaseEntity, SensorEntity):
self._attr_unique_id = f"{self._device_name}_last_temp_datetime" self._attr_unique_id = f"{self._device_name}_last_temp_datetime"
@callback @callback
async def async_my_climate_changed(self, event: Event): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", event.origin.name) _LOGGER.debug(
"%s - climate state change",
event.origin.name if event and event.origin else None,
)
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_mesure
@@ -353,9 +371,12 @@ class LastExtTemperatureSensor(VersatileThermostatBaseEntity, SensorEntity):
self._attr_unique_id = f"{self._device_name}_last_ext_temp_datetime" self._attr_unique_id = f"{self._device_name}_last_ext_temp_datetime"
@callback @callback
async def async_my_climate_changed(self, event: Event): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", event.origin.name) _LOGGER.debug(
"%s - climate state change",
event.origin.name if event and event.origin else None,
)
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_mesure

View File

@@ -1,5 +1,4 @@
""" Some common resources """ """ Some common resources """
from typing import Mapping
from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock
from homeassistant.core import HomeAssistant, Event, EVENT_STATE_CHANGED, State from homeassistant.core import HomeAssistant, Event, EVENT_STATE_CHANGED, State
@@ -7,22 +6,21 @@ from homeassistant.const import UnitOfTemperature, STATE_ON, STATE_OFF
from homeassistant.config_entries import ConfigEntryState from homeassistant.config_entries import ConfigEntryState
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import Entity
from pytest_homeassistant_custom_component.common import MockConfigEntry
from ..climate import VersatileThermostat
from ..const import *
from homeassistant.components.climate import ( from homeassistant.components.climate import (
ClimateEntity, ClimateEntity,
DOMAIN as CLIMATE_DOMAIN, DOMAIN as CLIMATE_DOMAIN,
ATTR_PRESET_MODE,
HVACMode, HVACMode,
HVACAction, HVACAction,
ClimateEntityFeature, ClimateEntityFeature,
) )
from .const import ( from pytest_homeassistant_custom_component.common import MockConfigEntry
from ..climate import VersatileThermostat
from ..const import * # pylint: disable=wildcard-import, unused-wildcard-import
from .const import ( # pylint: disable=unused-import
MOCK_TH_OVER_SWITCH_USER_CONFIG, MOCK_TH_OVER_SWITCH_USER_CONFIG,
MOCK_TH_OVER_CLIMATE_USER_CONFIG, MOCK_TH_OVER_CLIMATE_USER_CONFIG,
MOCK_TH_OVER_SWITCH_TYPE_CONFIG, MOCK_TH_OVER_SWITCH_TYPE_CONFIG,
@@ -81,60 +79,74 @@ class MockClimate(ClimateEntity):
class MagicMockClimate(MagicMock): class MagicMockClimate(MagicMock):
"""A Magic Mock class for a underlying climate entity"""
@property @property
def temperature_unit(self): def temperature_unit(self): # pylint: disable=missing-function-docstring
return UnitOfTemperature.CELSIUS return UnitOfTemperature.CELSIUS
@property @property
def hvac_mode(self): def hvac_mode(self): # pylint: disable=missing-function-docstring
return HVACMode.HEAT return HVACMode.HEAT
@property @property
def hvac_action(self): def hvac_action(self): # pylint: disable=missing-function-docstring
return HVACAction.IDLE return HVACAction.IDLE
@property @property
def target_temperature(self): def target_temperature(self): # pylint: disable=missing-function-docstring
return 15 return 15
@property @property
def current_temperature(self): def current_temperature(self): # pylint: disable=missing-function-docstring
return 14 return 14
@property @property
def target_temperature_step(self) -> float | None: def target_temperature_step( # pylint: disable=missing-function-docstring
self,
) -> float | None:
return 0.5 return 0.5
@property @property
def target_temperature_high(self) -> float | None: def target_temperature_high( # pylint: disable=missing-function-docstring
self,
) -> float | None:
return 35 return 35
@property @property
def target_temperature_low(self) -> float | None: def target_temperature_low( # pylint: disable=missing-function-docstring
self,
) -> float | None:
return 7 return 7
@property @property
def hvac_modes(self) -> list[str] | None: def hvac_modes( # pylint: disable=missing-function-docstring
self,
) -> list[str] | None:
return [HVACMode.HEAT, HVACMode.OFF, HVACMode.COOL] return [HVACMode.HEAT, HVACMode.OFF, HVACMode.COOL]
@property @property
def fan_modes(self) -> list[str] | None: def fan_modes( # pylint: disable=missing-function-docstring
self,
) -> list[str] | None:
return None return None
@property @property
def swing_modes(self) -> list[str] | None: def swing_modes( # pylint: disable=missing-function-docstring
self,
) -> list[str] | None:
return None return None
@property @property
def fan_mode(self) -> str | None: def fan_mode(self) -> str | None: # pylint: disable=missing-function-docstring
return None return None
@property @property
def swing_mode(self) -> str | None: def swing_mode(self) -> str | None: # pylint: disable=missing-function-docstring
return None return None
@property @property
def supported_features(self): def supported_features(self): # pylint: disable=missing-function-docstring
return ClimateEntityFeature.TARGET_TEMPERATURE return ClimateEntityFeature.TARGET_TEMPERATURE
@@ -149,16 +161,23 @@ async def create_thermostat(
await hass.config_entries.async_setup(entry.entry_id) await hass.config_entries.async_setup(entry.entry_id)
assert entry.state is ConfigEntryState.LOADED assert entry.state is ConfigEntryState.LOADED
def find_my_entity(entity_id) -> ClimateEntity: # def find_my_entity(entity_id) -> ClimateEntity:
"""Find my new entity""" # """Find my new entity"""
component: EntityComponent[ClimateEntity] = hass.data[CLIMATE_DOMAIN] # component: EntityComponent[ClimateEntity] = hass.data[CLIMATE_DOMAIN]
for entity in component.entities: # for entity in component.entities:
if entity.entity_id == entity_id: # if entity.entity_id == entity_id:
return entity # return entity
entity = find_my_entity(entity_id) return search_entity(hass, entity_id, CLIMATE_DOMAIN)
return entity
def search_entity(hass: HomeAssistant, entity_id, domain) -> Entity:
"""Search and return the entity in the domain"""
component = hass.data[domain]
for entity in component.entities:
if entity.entity_id == entity_id:
return entity
return None
async def send_temperature_change_event(entity: VersatileThermostat, new_temp, date): async def send_temperature_change_event(entity: VersatileThermostat, new_temp, date):
@@ -177,6 +196,24 @@ async def send_temperature_change_event(entity: VersatileThermostat, new_temp, d
return await entity._async_temperature_changed(temp_event) return await entity._async_temperature_changed(temp_event)
async def send_ext_temperature_change_event(
entity: VersatileThermostat, new_temp, date
):
"""Sending a new external temperature event simulating a change on temperature sensor"""
temp_event = Event(
EVENT_STATE_CHANGED,
{
"new_state": State(
entity_id=entity.entity_id,
state=new_temp,
last_changed=date,
last_updated=date,
)
},
)
return await entity._async_ext_temperature_changed(temp_event)
async def send_power_change_event(entity: VersatileThermostat, new_power, date): async def send_power_change_event(entity: VersatileThermostat, new_power, date):
"""Sending a new power event simulating a change on power sensor""" """Sending a new power event simulating a change on power sensor"""
power_event = Event( power_event = Event(
@@ -234,7 +271,57 @@ async def send_window_change_event(
return ret return ret
def get_tz(hass): async def send_motion_change_event(
entity: VersatileThermostat, new_state: bool, old_state: bool, date
):
"""Sending a new motion event simulating a change on the window state"""
motion_event = Event(
EVENT_STATE_CHANGED,
{
"new_state": State(
entity_id=entity.entity_id,
state=STATE_ON if new_state else STATE_OFF,
last_changed=date,
last_updated=date,
),
"old_state": State(
entity_id=entity.entity_id,
state=STATE_ON if old_state else STATE_OFF,
last_changed=date,
last_updated=date,
),
},
)
ret = await entity._async_motion_changed(motion_event)
return ret
async def send_presence_change_event(
entity: VersatileThermostat, new_state: bool, old_state: bool, date
):
"""Sending a new presence event simulating a change on the window state"""
presence_event = Event(
EVENT_STATE_CHANGED,
{
"new_state": State(
entity_id=entity.entity_id,
state=STATE_ON if new_state else STATE_OFF,
last_changed=date,
last_updated=date,
),
"old_state": State(
entity_id=entity.entity_id,
state=STATE_ON if old_state else STATE_OFF,
last_changed=date,
last_updated=date,
),
},
)
ret = await entity._async_presence_changed(presence_event)
return ret
def get_tz(hass: HomeAssistant):
"""Get the current timezone""" """Get the current timezone"""
return dt_util.get_time_zone(hass.config.time_zone) return dt_util.get_time_zone(hass.config.time_zone)
@@ -269,3 +356,4 @@ async def send_climate_change_event(
}, },
) )
ret = await entity._async_climate_changed(climate_event) ret = await entity._async_climate_changed(climate_event)
return ret

View File

@@ -18,7 +18,7 @@ from unittest.mock import patch
import pytest import pytest
from homeassistant.core import HomeAssistant, StateMachine from homeassistant.core import StateMachine
from custom_components.versatile_thermostat.config_flow import ( from custom_components.versatile_thermostat.config_flow import (
VersatileThermostatBaseConfigFlow, VersatileThermostatBaseConfigFlow,
@@ -28,13 +28,14 @@ from custom_components.versatile_thermostat.climate import (
VersatileThermostat, VersatileThermostat,
) )
pytest_plugins = "pytest_homeassistant_custom_component" pytest_plugins = "pytest_homeassistant_custom_component" # pylint: disable=invalid-name
# This fixture enables loading custom integrations in all tests. # This fixture enables loading custom integrations in all tests.
# Remove to enable selective use of this fixture # Remove to enable selective use of this fixture
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def auto_enable_custom_integrations(enable_custom_integrations): def auto_enable_custom_integrations(enable_custom_integrations):
"""Enable all integration in tests"""
yield yield
@@ -50,6 +51,17 @@ def skip_notifications_fixture():
yield yield
@pytest.fixture(name="skip_turn_on_off_heater")
def skip_turn_on_off_heater():
"""Skip turning on and off the heater"""
with patch(
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_heater_turn_on"
), patch(
"custom_components.versatile_thermostat.climate.VersatileThermostat._async_underlying_entity_turn_off"
):
yield
# This fixture is used to bypass the validate_input function in config_flow # This fixture is used to bypass the validate_input function in config_flow
# NOT USED Now (keeped for memory) # NOT USED Now (keeped for memory)
@pytest.fixture(name="skip_validate_input") @pytest.fixture(name="skip_validate_input")

View File

@@ -0,0 +1,503 @@
""" Test the normal start of a Thermostat """
from unittest.mock import patch
from datetime import timedelta, datetime
from homeassistant.core import HomeAssistant
from homeassistant.components.climate import HVACMode
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from pytest_homeassistant_custom_component.common import MockConfigEntry
from ..climate import VersatileThermostat
from ..binary_sensor import (
SecurityBinarySensor,
OverpoweringBinarySensor,
WindowBinarySensor,
MotionBinarySensor,
PresenceBinarySensor,
)
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
async def test_security_binary_sensors(
hass: HomeAssistant,
skip_hass_states_is_state,
skip_turn_on_off_heater,
skip_send_event,
):
"""Test the security binary sensors in thermostat 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: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"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_switch",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
},
)
entity: VersatileThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
security_binary_sensor: SecurityBinarySensor = search_entity(
hass, "binary_sensor.theoverswitchmockname_security_state", "binary_sensor"
)
assert security_binary_sensor
now: datetime = datetime.now(tz=get_tz(hass))
# Security should be disabled
await entity.async_set_preset_mode(PRESET_COMFORT)
await entity.async_set_hvac_mode(HVACMode.HEAT)
assert security_binary_sensor.state == STATE_OFF
assert security_binary_sensor.device_class == BinarySensorDeviceClass.SAFETY
# Set temperature in the past
event_timestamp = now - timedelta(minutes=6)
# set temperature to 15 so that on_percent will be > security_min_on_percent (0.2)
await send_temperature_change_event(entity, 15, event_timestamp)
assert entity.security_state is True
# Simulate the event reception
await security_binary_sensor.async_my_climate_changed()
assert security_binary_sensor.state == STATE_ON
# set temperature now
await send_temperature_change_event(entity, 15, now)
assert entity.security_state is False
# Simulate the event reception
await security_binary_sensor.async_my_climate_changed()
assert security_binary_sensor.state == STATE_OFF
async def test_overpowering_binary_sensors(
hass: HomeAssistant,
skip_hass_states_is_state,
skip_turn_on_off_heater,
skip_send_event,
):
"""Test the overpowering binary sensors in thermostat 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: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 19,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: True,
CONF_USE_PRESENCE_FEATURE: False,
CONF_HEATER: "switch.mock_switch",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_POWER_SENSOR: "sensor.mock_power_sensor",
CONF_MAX_POWER_SENSOR: "sensor.mock_power_max_sensor",
CONF_DEVICE_POWER: 100,
CONF_PRESET_POWER: 12,
},
)
entity: VersatileThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
overpowering_binary_sensor: OverpoweringBinarySensor = search_entity(
hass, "binary_sensor.theoverswitchmockname_overpowering_state", "binary_sensor"
)
assert overpowering_binary_sensor
now: datetime = datetime.now(tz=get_tz(hass))
# Overpowering should be not set because poer have not been received
await entity.async_set_preset_mode(PRESET_COMFORT)
await entity.async_set_hvac_mode(HVACMode.HEAT)
await send_temperature_change_event(entity, 15, now)
assert await entity.check_overpowering() is False
assert entity.overpowering_state is None
await overpowering_binary_sensor.async_my_climate_changed()
assert overpowering_binary_sensor.state is STATE_OFF
assert overpowering_binary_sensor.device_class == BinarySensorDeviceClass.POWER
await send_power_change_event(entity, 100, now)
await send_max_power_change_event(entity, 150, now)
assert await entity.check_overpowering() is True
assert entity.overpowering_state is True
# Simulate the event reception
await overpowering_binary_sensor.async_my_climate_changed()
assert overpowering_binary_sensor.state == STATE_ON
# set max power to a low value
await send_max_power_change_event(entity, 201, now)
assert await entity.check_overpowering() is False
assert entity.overpowering_state is False
# Simulate the event reception
await overpowering_binary_sensor.async_my_climate_changed()
assert overpowering_binary_sensor.state == STATE_OFF
async def test_window_binary_sensors(
hass: HomeAssistant,
skip_hass_states_is_state,
skip_turn_on_off_heater,
skip_send_event,
):
"""Test the window binary sensors in thermostat 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: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 19,
CONF_USE_WINDOW_FEATURE: True,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_HEATER: "switch.mock_switch",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
CONF_WINDOW_DELAY: 0, # important to not been obliged to wait
},
)
entity: VersatileThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
window_binary_sensor: WindowBinarySensor = search_entity(
hass, "binary_sensor.theoverswitchmockname_window_state", "binary_sensor"
)
assert window_binary_sensor
now: datetime = datetime.now(tz=get_tz(hass))
# Overpowering should be not set because poer have not been received
await entity.async_set_preset_mode(PRESET_COMFORT)
await entity.async_set_hvac_mode(HVACMode.HEAT)
await send_temperature_change_event(entity, 15, now)
assert entity.window_state is None
await window_binary_sensor.async_my_climate_changed()
assert window_binary_sensor.state is STATE_OFF
assert window_binary_sensor.device_class == BinarySensorDeviceClass.WINDOW
# Open the window
with patch("homeassistant.helpers.condition.state", return_value=True):
try_window_condition = await send_window_change_event(entity, True, False, now)
# simulate the call to try_window_condition
await try_window_condition(None)
assert entity.window_state is STATE_ON
# Simulate the event reception
await window_binary_sensor.async_my_climate_changed()
assert window_binary_sensor.state == STATE_ON
# close the window
with patch("homeassistant.helpers.condition.state", return_value=True):
try_window_condition = await send_window_change_event(entity, False, True, now)
# simulate the call to try_window_condition
await try_window_condition(None)
assert entity.window_state is STATE_OFF
# Simulate the event reception
await window_binary_sensor.async_my_climate_changed()
assert window_binary_sensor.state == STATE_OFF
async def test_motion_binary_sensors(
hass: HomeAssistant,
skip_hass_states_is_state,
skip_turn_on_off_heater,
skip_send_event,
):
"""Test the motion binary sensors in thermostat 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: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 19,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: True,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_HEATER: "switch.mock_switch",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_MOTION_SENSOR: "binary_sensor.mock_motion_sensor",
CONF_MOTION_DELAY: 0, # important to not been obliged to wait
},
)
entity: VersatileThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
motion_binary_sensor: MotionBinarySensor = search_entity(
hass, "binary_sensor.theoverswitchmockname_motion_state", "binary_sensor"
)
assert motion_binary_sensor
now: datetime = datetime.now(tz=get_tz(hass))
# Overpowering should be not set because poer have not been received
await entity.async_set_preset_mode(PRESET_COMFORT)
await entity.async_set_hvac_mode(HVACMode.HEAT)
await send_temperature_change_event(entity, 15, now)
assert entity.motion_state is None
await motion_binary_sensor.async_my_climate_changed()
assert motion_binary_sensor.state is STATE_OFF
assert motion_binary_sensor.device_class == BinarySensorDeviceClass.MOTION
# Detect motion
with patch("homeassistant.helpers.condition.state", return_value=True):
try_motion_condition = await send_motion_change_event(entity, True, False, now)
# simulate the call to try_window_condition
await try_motion_condition(None)
assert entity.motion_state is STATE_ON
# Simulate the event reception
await motion_binary_sensor.async_my_climate_changed()
assert motion_binary_sensor.state == STATE_ON
# Undetect motion
with patch("homeassistant.helpers.condition.state", return_value=True):
try_motion_condition = await send_motion_change_event(entity, False, True, now)
# simulate the call to try_motion_condition
await try_motion_condition(None)
assert entity.motion_state is STATE_OFF
# Simulate the event reception
await motion_binary_sensor.async_my_climate_changed()
assert motion_binary_sensor.state == STATE_OFF
async def test_presence_binary_sensors(
hass: HomeAssistant,
skip_hass_states_is_state,
skip_turn_on_off_heater,
skip_send_event,
):
"""Test the presence binary sensors in thermostat 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: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 19,
"eco_away_temp": 12,
"comfort_away_temp": 13,
"boost_away_temp": 14,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: True,
CONF_HEATER: "switch.mock_switch",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_PRESENCE_SENSOR: "binary_sensor.mock_presence_sensor",
},
)
entity: VersatileThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
presence_binary_sensor: PresenceBinarySensor = search_entity(
hass, "binary_sensor.theoverswitchmockname_presence_state", "binary_sensor"
)
assert presence_binary_sensor
now: datetime = datetime.now(tz=get_tz(hass))
# Overpowering should be not set because poer have not been received
await entity.async_set_preset_mode(PRESET_COMFORT)
await entity.async_set_hvac_mode(HVACMode.HEAT)
await send_temperature_change_event(entity, 15, now)
assert entity.presence_state is None
await presence_binary_sensor.async_my_climate_changed()
assert presence_binary_sensor.state is STATE_OFF
assert presence_binary_sensor.device_class == BinarySensorDeviceClass.PRESENCE
# Detect motion
await send_presence_change_event(entity, True, False, now)
assert entity.presence_state is STATE_ON
# Simulate the event reception
await presence_binary_sensor.async_my_climate_changed()
assert presence_binary_sensor.state == STATE_ON
# Undetect motion
await send_presence_change_event(entity, False, True, now)
assert entity.presence_state is STATE_OFF
# Simulate the event reception
await presence_binary_sensor.async_my_climate_changed()
assert presence_binary_sensor.state == STATE_OFF
async def test_binary_sensors_over_climate_minimal(
hass: HomeAssistant,
skip_hass_states_is_state,
skip_turn_on_off_heater,
skip_send_event,
):
"""Test the binary sensors with thermostat over climate type"""
the_mock_underlying = MagicMockClimate()
with patch(
"custom_components.versatile_thermostat.climate.VersatileThermostat.find_underlying_climate",
return_value=the_mock_underlying,
):
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverClimateMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOverClimateMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 19,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
},
)
entity: VersatileThermostat = await create_thermostat(
hass, entry, "climate.theoverclimatemockname"
)
assert entity
assert entity.is_over_climate
security_binary_sensor: SecurityBinarySensor = search_entity(
hass, "binary_sensor.theoverclimatemockname_security_state", "binary_sensor"
)
assert security_binary_sensor is not None
overpowering_binary_sensor: OverpoweringBinarySensor = search_entity(
hass, "binary_sensor.theoverclimatemockname_overpowering_state", "binary_sensor"
)
assert overpowering_binary_sensor is None
window_binary_sensor: WindowBinarySensor = search_entity(
hass, "binary_sensor.theoverclimatemockname_window_state", "binary_sensor"
)
assert window_binary_sensor is None
motion_binary_sensor: MotionBinarySensor = search_entity(
hass, "binary_sensor.theoverclimatemockname_motion_state", "binary_sensor"
)
assert motion_binary_sensor is None
presence_binary_sensor: PresenceBinarySensor = search_entity(
hass, "binary_sensor.theoverclimatemockname_presence_state", "binary_sensor"
)
assert presence_binary_sensor is None

View File

@@ -4,11 +4,7 @@ from homeassistant import data_entry_flow
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.config_entries import SOURCE_USER, ConfigEntry from homeassistant.config_entries import SOURCE_USER, ConfigEntry
import pytest
from pytest_homeassistant_custom_component.common import MockConfigEntry, load_fixture
from custom_components.versatile_thermostat.const import DOMAIN from custom_components.versatile_thermostat.const import DOMAIN
from custom_components.versatile_thermostat import VersatileThermostatAPI
from .const import ( from .const import (
MOCK_TH_OVER_SWITCH_USER_CONFIG, MOCK_TH_OVER_SWITCH_USER_CONFIG,
@@ -41,7 +37,7 @@ async def test_show_form(hass: HomeAssistant) -> None:
assert result["step_id"] == SOURCE_USER assert result["step_id"] == SOURCE_USER
async def test_user_config_flow_over_switch(hass, skip_hass_states_get): async def test_user_config_flow_over_switch(hass: HomeAssistant, skip_hass_states_get):
"""Test the config flow with all thermostat_over_switch features""" """Test the config flow with all thermostat_over_switch features"""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER} DOMAIN, context={"source": SOURCE_USER}
@@ -138,7 +134,7 @@ async def test_user_config_flow_over_switch(hass, skip_hass_states_get):
assert isinstance(result["result"], ConfigEntry) assert isinstance(result["result"], ConfigEntry)
async def test_user_config_flow_over_climate(hass, skip_hass_states_get): async def test_user_config_flow_over_climate(hass: HomeAssistant, skip_hass_states_get):
"""Test the config flow with all thermostat_over_climate features and no additional features""" """Test the config flow with all thermostat_over_climate features and no additional features"""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER} DOMAIN, context={"source": SOURCE_USER}

View File

@@ -1,5 +1,5 @@
""" Test the Power management """ """ Test the Power management """
from unittest.mock import patch, call, MagicMock from unittest.mock import patch, call
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
from datetime import datetime, timedelta from datetime import datetime, timedelta
@@ -44,7 +44,7 @@ async def test_power_management_hvac_off(
CONF_POWER_SENSOR: "sensor.mock_power_sensor", CONF_POWER_SENSOR: "sensor.mock_power_sensor",
CONF_MAX_POWER_SENSOR: "sensor.mock_power_max_sensor", CONF_MAX_POWER_SENSOR: "sensor.mock_power_max_sensor",
CONF_DEVICE_POWER: 100, CONF_DEVICE_POWER: 100,
CONF_PRESET_POWER: "eco", CONF_PRESET_POWER: 12,
}, },
) )
@@ -394,7 +394,7 @@ async def test_power_management_energy_over_climate(
hass, entry, "climate.theoverclimatemockname" hass, entry, "climate.theoverclimatemockname"
) )
assert entity assert entity
assert entity._is_over_climate assert entity.is_over_climate
now = datetime.now(tz=get_tz(hass)) now = datetime.now(tz=get_tz(hass))
await send_temperature_change_event(entity, 15, now) await send_temperature_change_event(entity, 15, now)

View File

@@ -19,7 +19,7 @@ async def test_security_feature(hass: HomeAssistant, skip_hass_states_is_state):
6. check that security is off and preset is changed to boost 6. check that security is off and preset is changed to boost
""" """
tz = get_tz(hass) tz = get_tz(hass) # pylint: disable=invalid-name
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
@@ -93,7 +93,7 @@ async def test_security_feature(hass: HomeAssistant, skip_hass_states_is_state):
# set temperature to 15 so that on_percent will be > security_min_on_percent (0.2) # set temperature to 15 so that on_percent will be > security_min_on_percent (0.2)
await send_temperature_change_event(entity, 15, event_timestamp) await send_temperature_change_event(entity, 15, event_timestamp)
assert entity._security_state is True assert entity.security_state is True
assert entity.preset_mode == PRESET_SECURITY assert entity.preset_mode == PRESET_SECURITY
assert entity._saved_preset_mode == PRESET_COMFORT assert entity._saved_preset_mode == PRESET_COMFORT
assert entity._prop_algorithm.on_percent == 0.1 assert entity._prop_algorithm.on_percent == 0.1

View File

@@ -0,0 +1,375 @@
""" Test the normal start of a Thermostat """
from datetime import timedelta, datetime
from homeassistant.core import HomeAssistant
from homeassistant.components.climate import HVACMode
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorStateClass,
)
from homeassistant.const import UnitOfTime, UnitOfPower, UnitOfEnergy, PERCENTAGE
from pytest_homeassistant_custom_component.common import MockConfigEntry
from ..climate import VersatileThermostat
from ..sensor import (
EnergySensor,
MeanPowerSensor,
OnPercentSensor,
OnTimeSensor,
OffTimeSensor,
LastTemperatureSensor,
LastExtTemperatureSensor,
)
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
async def test_sensors_over_switch(
hass: HomeAssistant,
skip_hass_states_is_state,
skip_turn_on_off_heater,
skip_send_event,
):
"""Test the sensors with a thermostat avec 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: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"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_switch",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_DEVICE_POWER: 200,
},
)
entity: VersatileThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
energy_sensor: EnergySensor = search_entity(
hass, "sensor.theoverswitchmockname_energy", "sensor"
)
assert energy_sensor
mean_power_sensor: MeanPowerSensor = search_entity(
hass, "sensor.theoverswitchmockname_mean_power_cycle", "sensor"
)
assert mean_power_sensor
on_percent_sensor: OnPercentSensor = search_entity(
hass, "sensor.theoverswitchmockname_power_percent", "sensor"
)
assert on_percent_sensor
on_time_sensor: OnTimeSensor = search_entity(
hass, "sensor.theoverswitchmockname_on_time", "sensor"
)
assert on_time_sensor
off_time_sensor: OffTimeSensor = search_entity(
hass, "sensor.theoverswitchmockname_off_time", "sensor"
)
assert off_time_sensor
last_temperature_sensor: LastTemperatureSensor = search_entity(
hass, "sensor.theoverswitchmockname_last_temperature_date", "sensor"
)
assert last_temperature_sensor
last_ext_temperature_sensor: LastExtTemperatureSensor = search_entity(
hass, "sensor.theoverswitchmockname_last_external_temperature_date", "sensor"
)
assert last_ext_temperature_sensor
# Simulate the event reception
await energy_sensor.async_my_climate_changed()
assert energy_sensor.state == 0.0
await mean_power_sensor.async_my_climate_changed()
assert mean_power_sensor.state == 0.0
await on_percent_sensor.async_my_climate_changed()
assert on_percent_sensor.state == 0.0
await on_time_sensor.async_my_climate_changed()
assert on_time_sensor.state == 0.0
await off_time_sensor.async_my_climate_changed()
assert off_time_sensor.state == 300.0
await last_temperature_sensor.async_my_climate_changed()
assert last_temperature_sensor.state is not None
await last_ext_temperature_sensor.async_my_climate_changed()
assert last_ext_temperature_sensor.state is not None
last_temp_date = last_temperature_sensor.state
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
event_timestamp = now - timedelta(minutes=1)
# Start the heater to get some values
await entity.async_set_preset_mode(PRESET_COMFORT)
await entity.async_set_hvac_mode(HVACMode.HEAT)
await send_temperature_change_event(entity, 15, event_timestamp)
await send_ext_temperature_change_event(entity, 5, event_timestamp)
entity.incremente_energy()
await energy_sensor.async_my_climate_changed()
assert energy_sensor.state == 16.667
assert energy_sensor.device_class == SensorDeviceClass.ENERGY
assert energy_sensor.state_class == SensorStateClass.TOTAL_INCREASING
# because device_power is 200
assert energy_sensor.unit_of_measurement == UnitOfEnergy.WATT_HOUR
await mean_power_sensor.async_my_climate_changed()
assert mean_power_sensor.state == 200.0
assert mean_power_sensor.device_class == SensorDeviceClass.POWER
assert mean_power_sensor.state_class == SensorStateClass.MEASUREMENT
# because device_power is 200
assert mean_power_sensor.unit_of_measurement == UnitOfPower.WATT
await on_percent_sensor.async_my_climate_changed()
assert on_percent_sensor.state == 100.0
assert on_percent_sensor.unit_of_measurement == PERCENTAGE
await on_time_sensor.async_my_climate_changed()
assert on_time_sensor.state == 300.0
assert on_time_sensor.device_class == SensorDeviceClass.DURATION
assert on_time_sensor.state_class == SensorStateClass.MEASUREMENT
assert on_time_sensor.unit_of_measurement == UnitOfTime.SECONDS
await off_time_sensor.async_my_climate_changed()
assert off_time_sensor.state == 0.0
assert off_time_sensor.device_class == SensorDeviceClass.DURATION
assert off_time_sensor.state_class == SensorStateClass.MEASUREMENT
assert off_time_sensor.unit_of_measurement == UnitOfTime.SECONDS
await last_temperature_sensor.async_my_climate_changed()
assert (
last_temperature_sensor.state is not None
and last_temperature_sensor.state != last_temp_date
)
assert last_temperature_sensor.device_class == SensorDeviceClass.TIMESTAMP
await last_ext_temperature_sensor.async_my_climate_changed()
assert (
last_ext_temperature_sensor.state is not None
and last_ext_temperature_sensor.state != last_temp_date
)
assert last_ext_temperature_sensor.device_class == SensorDeviceClass.TIMESTAMP
async def test_sensors_over_climate(
hass: HomeAssistant,
skip_hass_states_is_state,
skip_turn_on_off_heater,
skip_send_event,
):
"""Test the sensors with thermostat over climate type"""
the_mock_underlying = MagicMockClimate()
with patch(
"custom_components.versatile_thermostat.climate.VersatileThermostat.find_underlying_climate",
return_value=the_mock_underlying,
):
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverClimateMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOverClimateMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 19,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: True,
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_POWER_SENSOR: "sensor.mock_power_sensor",
CONF_MAX_POWER_SENSOR: "sensor.mock_power_max_sensor",
CONF_DEVICE_POWER: 1.5,
CONF_PRESET_POWER: 12,
},
)
entity: VersatileThermostat = await create_thermostat(
hass, entry, "climate.theoverclimatemockname"
)
assert entity
assert entity.is_over_climate
energy_sensor: EnergySensor = search_entity(
hass, "sensor.theoverclimatemockname_energy", "sensor"
)
assert energy_sensor
last_temperature_sensor: LastTemperatureSensor = search_entity(
hass, "sensor.theoverclimatemockname_last_temperature_date", "sensor"
)
assert last_temperature_sensor
last_ext_temperature_sensor: LastExtTemperatureSensor = search_entity(
hass, "sensor.theoverclimatemockname_last_external_temperature_date", "sensor"
)
assert last_ext_temperature_sensor
# Simulate the event reception
await energy_sensor.async_my_climate_changed()
assert energy_sensor.state == 0.0
await last_temperature_sensor.async_my_climate_changed()
assert last_temperature_sensor.state is not None
await last_ext_temperature_sensor.async_my_climate_changed()
assert last_ext_temperature_sensor.state is not None
last_temp_date = last_temperature_sensor.state
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
event_timestamp = now - timedelta(minutes=1)
# Start the heater to get some values
await entity.async_set_preset_mode(PRESET_COMFORT)
await entity.async_set_hvac_mode(HVACMode.HEAT)
await send_temperature_change_event(entity, 15, event_timestamp)
await send_ext_temperature_change_event(entity, 5, event_timestamp)
# to add energy we must have HVACAction underlying climate event
# Send a climate_change event with HVACAction=HEATING
event_timestamp = now - timedelta(minutes=60)
await send_climate_change_event(
entity,
new_hvac_mode=HVACMode.HEAT,
old_hvac_mode=HVACMode.HEAT,
new_hvac_action=HVACAction.HEATING,
old_hvac_action=HVACAction.OFF,
date=event_timestamp,
)
# Send a climate_change event with HVACAction=IDLE (end of heating)
await send_climate_change_event(
entity,
new_hvac_mode=HVACMode.HEAT,
old_hvac_mode=HVACMode.HEAT,
new_hvac_action=HVACAction.IDLE,
old_hvac_action=HVACAction.HEATING,
date=now,
)
# 60 minutes heating with 1.5 kW heating -> 1.5 kWh
await energy_sensor.async_my_climate_changed()
assert energy_sensor.state == 1.5
assert energy_sensor.device_class == SensorDeviceClass.ENERGY
assert energy_sensor.state_class == SensorStateClass.TOTAL_INCREASING
# because device_power is 1.5 kW
assert energy_sensor.unit_of_measurement == UnitOfEnergy.KILO_WATT_HOUR
entity.incremente_energy()
await energy_sensor.async_my_climate_changed()
assert energy_sensor.state == 3.0
await last_temperature_sensor.async_my_climate_changed()
assert (
last_temperature_sensor.state is not None
and last_temperature_sensor.state != last_temp_date
)
assert last_temperature_sensor.device_class == SensorDeviceClass.TIMESTAMP
await last_ext_temperature_sensor.async_my_climate_changed()
assert (
last_ext_temperature_sensor.state is not None
and last_ext_temperature_sensor.state != last_temp_date
)
assert last_ext_temperature_sensor.device_class == SensorDeviceClass.TIMESTAMP
async def test_sensors_over_climate_minimal(
hass: HomeAssistant,
skip_hass_states_is_state,
skip_turn_on_off_heater,
skip_send_event,
):
"""Test the sensors with thermostat over climate type"""
the_mock_underlying = MagicMockClimate()
with patch(
"custom_components.versatile_thermostat.climate.VersatileThermostat.find_underlying_climate",
return_value=the_mock_underlying,
):
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverClimateMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOverClimateMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 19,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
},
)
entity: VersatileThermostat = await create_thermostat(
hass, entry, "climate.theoverclimatemockname"
)
assert entity
assert entity.is_over_climate
energy_sensor: EnergySensor = search_entity(
hass, "sensor.theoverclimatemockname_energy", "sensor"
)
assert energy_sensor is None
last_temperature_sensor: LastTemperatureSensor = search_entity(
hass, "sensor.theoverclimatemockname_last_temperature_date", "sensor"
)
assert last_temperature_sensor
last_ext_temperature_sensor: LastExtTemperatureSensor = search_entity(
hass, "sensor.theoverclimatemockname_last_external_temperature_date", "sensor"
)
assert last_ext_temperature_sensor

View File

@@ -12,7 +12,7 @@ from pytest_homeassistant_custom_component.common import MockConfigEntry
from ..climate import VersatileThermostat from ..climate import VersatileThermostat
from .commons import * from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
async def test_over_switch_full_start(hass: HomeAssistant, skip_hass_states_is_state): async def test_over_switch_full_start(hass: HomeAssistant, skip_hass_states_is_state):

View File

@@ -2,7 +2,6 @@
from unittest.mock import patch, call from unittest.mock import patch, call
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
from datetime import datetime from datetime import datetime
import time
import logging import logging

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB