Compare commits

...

5 Commits

Author SHA1 Message Date
Jean-Marc Collin a9c368d64c Release 6.2.0 2024-03-31 18:48:47 +00:00
Jean-Marc Collin 1595ff32a2 [#432] - Use valve number max value instead of 100 (#434)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-03-31 20:47:04 +02:00
Jean-Marc Collin c49545d9e3 [#398] - Add last_seen sensor to update temperature last datetime (#433)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-03-31 20:25:34 +02:00
Jean-Marc Collin f4598a407e Ajout incompatibilité Airwell 2024-03-31 16:57:32 +00:00
Jean-Marc Collin d96fe4bec7 Add Nodon module virtuak switch example 2024-03-30 07:51:22 +00:00
14 changed files with 357 additions and 33 deletions
+8
View File
@@ -6,6 +6,7 @@ logger:
custom_components.versatile_thermostat: info
custom_components.versatile_thermostat.underlyings: info
custom_components.versatile_thermostat.climate: info
custom_components.versatile_thermostat.base_thermostat: debug
# If you need to debug uncommment the line below (doc: https://www.home-assistant.io/integrations/debugpy/)
debugpy:
@@ -166,6 +167,13 @@ climate:
heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1
input_datetime:
fake_last_seen:
name: Last seen temp sensor
icon: mdi:update
has_date: true
has_time: true
recorder:
commit_interval: 1
include:
+39
View File
@@ -75,6 +75,7 @@
- [Dépannages](#dépannages)
- [Utilisation d'un Heatzy](#utilisation-dun-heatzy)
- [Utilisation d'un radiateur avec un fil pilote](#utilisation-dun-radiateur-avec-un-fil-pilote)
- [Utilisation d'un radiateur avec un fil pilote](#utilisation-dun-radiateur-avec-un-fil-pilote-1)
- [Seul le premier radiateur chauffe](#seul-le-premier-radiateur-chauffe)
- [Le radiateur chauffe alors que la température de consigne est dépassée ou ne chauffe pas alors que la température de la pièce est bien en-dessous de la consigne](#le-radiateur-chauffe-alors-que-la-température-de-consigne-est-dépassée-ou-ne-chauffe-pas-alors-que-la-température-de-la-pièce-est-bien-en-dessous-de-la-consigne)
- [Type `over_switch` ou `over_valve`](#type-over_switch-ou-over_valve)
@@ -235,6 +236,7 @@ Certains thermostat de type TRV sont réputés incompatibles avec le Versatile T
3. les thermostats de type Heatzy qui ne supportent pas les commandes de type set_temperature
4. les thermostats de type Rointe ont tendance a se réveiller tout seul. Le reste fonctionne normalement.
5. les TRV de type Aqara SRTS-A01 et MOES TV01-ZB qui n'ont pas le retour d'état `hvac_action` permettant de savoir si elle chauffe ou pas. Donc les retours d'état sont faussés, le reste à l'air fonctionnel.
6. La clim Airwell avec l'intégration "Midea AC LAN". Si 2 commandes de VTherm sont trop rapprochées, la clim s'arrête d'elle même.
# Pourquoi une nouvelle implémentation du thermostat ?
@@ -1497,6 +1499,43 @@ Exemple :
</details>
<details>
<summary>Utilisation d'un radiateur avec un module Nodon</summary>
## Utilisation d'un radiateur avec un fil pilote
Comme pour le Heatzy ci-dessus vous pouvez utiliser un switch virtuel qui va changer le preset de votre radiateur en fonction de l'état d'allumage du VTherm.
Exemple :
```
- platform: template
switches:
chauffage_chb_parents:
unique_id: chauffage_chb_parents
friendly_name: Chauffage chambre parents
value_template: "{{ is_state('select.fp_chb_parents_pilot_wire_mode', 'comfort') }}"
icon_template: >-
{% if is_state('select.fp_chb_parents_pilot_wire_mode', 'comfort') %}
mdi:radiator
{% elif is_state('select.fp_chb_parents_pilot_wire_mode', 'frost_protection') %}
mdi:snowflake
{% else %}
mdi:radiator-disabled
{% endif %}
turn_on:
service: select.select_option
target:
entity_id: select.fp_chb_parents_pilot_wire_mode
data:
option: comfort
turn_off:
service: select.select_option
target:
entity_id: select.fp_chb_parents_pilot_wire_mode
data:
option: eco
```
</details>
<details>
<summary>Seul le premier radiateur chauffe</summary>
+39
View File
@@ -76,6 +76,7 @@
- [Troubleshooting](#troubleshooting)
- [Using a Heatzy](#using-a-heatzy)
- [Using a Heatsink with a Pilot Wire](#using-a-heatsink-with-a-pilot-wire)
- [Using a heater with a Nodon module](#using-a-heater-with-a-nodon-module)
- [Only the first radiator heats](#only-the-first-radiator-heats)
- [The radiator heats up even though the setpoint temperature is exceeded or does not heat up even though the room temperature is well below the setpoint](#the-radiator-heats-up-even-though-the-setpoint-temperature-is-exceeded-or-does-not-heat-up-even-though-the-room-temperature-is-well-below-the-setpoint)
- [Type `over_switch` or `over_valve`](#type-over_switch-or-over_valve)
@@ -236,6 +237,7 @@ Some TRV type thermostats are known to be incompatible with the Versatile Thermo
3. Thermostat of type Heatzy which doesn't supports the set_temperature command.
4. Thermostats of type Rointe tends to awake alone even if VTherm turns it off. Others functions works fine.
5. TRV of type Aqara SRTS-A01 and MOES TV01-ZB which doesn't have the return state `hvac_action` allowing to know if it is heating or not. So return states are not available. Others features, seems to work normally.
6. The Airwell with the "Midea AC LAN" integration. If two orders are too close, the device shut off.
# Why another thermostat implementation ?
@@ -1488,6 +1490,43 @@ Example :
```
</details>
<details>
<summary>Using a heater with a Nodon</summary>
## Using a heater with a Nodon module
As for the heatzy module above you can use a virtual switch which will change the preset of your heater depending of the state of the VTherm.
Example :
```
- platform: template
switches:
chauffage_chb_parents:
unique_id: chauffage_chb_parents
friendly_name: Chauffage chambre parents
value_template: "{{ is_state('select.fp_chb_parents_pilot_wire_mode', 'comfort') }}"
icon_template: >-
{% if is_state('select.fp_chb_parents_pilot_wire_mode', 'comfort') %}
mdi:radiator
{% elif is_state('select.fp_chb_parents_pilot_wire_mode', 'frost_protection') %}
mdi:snowflake
{% else %}
mdi:radiator-disabled
{% endif %}
turn_on:
service: select.select_option
target:
entity_id: select.fp_chb_parents_pilot_wire_mode
data:
option: comfort
turn_off:
service: select.select_option
target:
entity_id: select.fp_chb_parents_pilot_wire_mode
data:
option: eco
```
</details>
<details>
<summary>Only the first radiator heats</summary>
@@ -68,6 +68,7 @@ from .const import (
DEVICE_MANUFACTURER,
CONF_POWER_SENSOR,
CONF_TEMP_SENSOR,
CONF_LAST_SEEN_TEMP_SENSOR,
CONF_EXTERNAL_TEMP_SENSOR,
CONF_MAX_POWER_SENSOR,
CONF_WINDOW_SENSOR,
@@ -242,6 +243,9 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
self._motion_call_cancel = None
self._cur_temp = None
self._ac_mode = None
self._temp_sensor_entity_id = None
self._last_seen_temp_sensor_entity_id = None
self._ext_temp_sensor_entity_id = None
self._last_ext_temperature_measure = None
self._last_temperature_measure = None
self._cur_ext_temp = None
@@ -393,6 +397,9 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
self._proportional_function = entry_infos.get(CONF_PROP_FUNCTION)
self._temp_sensor_entity_id = entry_infos.get(CONF_TEMP_SENSOR)
self._last_seen_temp_sensor_entity_id = entry_infos.get(
CONF_LAST_SEEN_TEMP_SENSOR
)
self._ext_temp_sensor_entity_id = entry_infos.get(CONF_EXTERNAL_TEMP_SENSOR)
self._power_sensor_entity_id = entry_infos.get(CONF_POWER_SENSOR)
self._max_power_sensor_entity_id = entry_infos.get(CONF_MAX_POWER_SENSOR)
@@ -574,6 +581,15 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
)
)
if self._last_seen_temp_sensor_entity_id:
self.async_on_remove(
async_track_state_change_event(
self.hass,
[self._last_seen_temp_sensor_entity_id],
self._async_last_seen_temperature_changed,
)
)
if self._ext_temp_sensor_entity_id:
self.async_on_remove(
async_track_state_change_event(
@@ -1445,6 +1461,44 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
await self.async_control_heating(force=False)
return dearm_window_auto
@callback
async def _async_last_seen_temperature_changed(self, event: Event):
"""Handle last seen temperature sensor changes."""
new_state: State = event.data.get("new_state")
_LOGGER.debug(
"%s - Last seen temperature changed. Event.new_state is %s",
self,
new_state,
)
if new_state is None or new_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN):
return
# try to extract the datetime (from state)
try:
# Convertir la chaîne au format ISO 8601 en objet datetime
self._last_temperature_measure = self.get_last_updated_date_or_now(
new_state
)
self.reset_last_change_time()
_LOGGER.debug(
"%s - new last_temperature_measure is now: %s",
self,
self._last_temperature_measure,
)
# try to restart if we were in safety mode
if self._security_state:
await self.check_safety()
except ValueError as err:
# La conversion a échoué, la chaîne n'est pas au format ISO 8601
_LOGGER.warning(
"%s - impossible to convert last seen datetime %s. Error is: %s",
self,
new_state.state,
err,
)
async def _async_ext_temperature_changed(self, event: Event):
"""Handle external temperature opf the sensor changes."""
new_state: State = event.data.get("new_state")
@@ -16,6 +16,10 @@ from homeassistant.components.input_number import (
DOMAIN as INPUT_NUMBER_DOMAIN,
)
from homeassistant.components.input_datetime import (
DOMAIN as INPUT_DATETIME_DOMAIN,
)
from homeassistant.components.person import DOMAIN as PERSON_DOMAIN
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
@@ -42,6 +46,11 @@ STEP_MAIN_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
vol.Required(CONF_TEMP_SENSOR): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[SENSOR_DOMAIN, INPUT_NUMBER_DOMAIN]),
),
vol.Optional(CONF_LAST_SEEN_TEMP_SENSOR): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=[SENSOR_DOMAIN, INPUT_DATETIME_DOMAIN]
),
),
vol.Required(CONF_CYCLE_MIN, default=5): cv.positive_int,
vol.Optional(CONF_DEVICE_POWER, default="1"): vol.Coerce(float),
vol.Required(CONF_USE_MAIN_CENTRAL_CONFIG, default=True): cv.boolean,
@@ -59,6 +59,7 @@ CONF_HEATER_3 = "heater_entity3_id"
CONF_HEATER_4 = "heater_entity4_id"
CONF_HEATER_KEEP_ALIVE = "heater_keep_alive"
CONF_TEMP_SENSOR = "temperature_sensor_entity_id"
CONF_LAST_SEEN_TEMP_SENSOR = "last_seen_temperature_sensor_entity_id"
CONF_EXTERNAL_TEMP_SENSOR = "external_temperature_sensor_entity_id"
CONF_POWER_SENSOR = "power_sensor_entity_id"
CONF_MAX_POWER_SENSOR = "max_power_sensor_entity_id"
@@ -14,6 +14,6 @@
"quality_scale": "silver",
"requirements": [],
"ssdp": [],
"version": "6.1.0",
"version": "6.2.0",
"zeroconf": []
}
@@ -37,7 +37,8 @@
"data": {
"name": "Name",
"thermostat_type": "Thermostat type",
"temperature_sensor_entity_id": "Room temperature sensor entity id",
"temperature_sensor_entity_id": "Room temperature",
"last_seen_temperature_sensor_entity_id": "Last seen room temperature datetime",
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)",
"temp_min": "Minimum temperature allowed",
@@ -49,6 +50,8 @@
"used_by_controls_central_boiler": "Used by central boiler. Check if this VTherm should have control on the central boiler"
},
"data_description": {
"temperature_sensor_entity_id": "Room temperature sensor entity id",
"last_seen_temperature_sensor_entity_id": "Last seen room temperature sensor entity id. Should be datetime sensor",
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id. Not used if central configuration is selected"
}
},
@@ -269,7 +272,8 @@
"data": {
"name": "Name",
"thermostat_type": "Thermostat type",
"temperature_sensor_entity_id": "Room temperature sensor entity id",
"temperature_sensor_entity_id": "Room temperature",
"last_seen_temperature_sensor_entity_id": "Last seen room temperature datetime",
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)",
"temp_min": "Minimum temperature allowed",
@@ -281,6 +285,8 @@
"used_by_controls_central_boiler": "Used by central boiler. Check if this VTherm should have control on the central boiler"
},
"data_description": {
"temperature_sensor_entity_id": "Room temperature sensor entity id",
"last_seen_temperature_sensor_entity_id": "Last seen room temperature sensor entity id. Should be datetime sensor",
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id. Not used if central configuration is selected"
}
},
@@ -572,4 +578,4 @@
}
}
}
}
}
@@ -37,7 +37,8 @@
"data": {
"name": "Name",
"thermostat_type": "Thermostat type",
"temperature_sensor_entity_id": "Room temperature sensor entity id",
"temperature_sensor_entity_id": "Room temperature",
"last_seen_temperature_sensor_entity_id": "Last seen room temperature datetime",
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)",
"temp_min": "Minimum temperature allowed",
@@ -49,6 +50,8 @@
"used_by_controls_central_boiler": "Used by central boiler. Check if this VTherm should have control on the central boiler"
},
"data_description": {
"temperature_sensor_entity_id": "Room temperature sensor entity id",
"last_seen_temperature_sensor_entity_id": "Last seen room temperature sensor entity id. Should be datetime sensor",
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id. Not used if central configuration is selected"
}
},
@@ -269,7 +272,8 @@
"data": {
"name": "Name",
"thermostat_type": "Thermostat type",
"temperature_sensor_entity_id": "Room temperature sensor entity id",
"temperature_sensor_entity_id": "Room temperature",
"last_seen_temperature_sensor_entity_id": "Last seen room temperature datetime",
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)",
"temp_min": "Minimum temperature allowed",
@@ -281,6 +285,8 @@
"used_by_controls_central_boiler": "Used by central boiler. Check if this VTherm should have control on the central boiler"
},
"data_description": {
"temperature_sensor_entity_id": "Room temperature sensor entity id",
"last_seen_temperature_sensor_entity_id": "Last seen room temperature sensor entity id. Should be datetime sensor",
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id. Not used if central configuration is selected"
}
},
@@ -572,4 +578,4 @@
}
}
}
}
}
@@ -37,8 +37,9 @@
"data": {
"name": "Nom",
"thermostat_type": "Type de thermostat",
"temperature_sensor_entity_id": "Température sensor entity id",
"external_temperature_sensor_entity_id": "Température exterieure sensor entity id",
"temperature_sensor_entity_id": "Capteur de température",
"last_seen_temperature_sensor_entity_id": "Dernière vue capteur de température",
"external_temperature_sensor_entity_id": "Capteur de température exterieure",
"cycle_min": "Durée du cycle (minutes)",
"temp_min": "Température minimale permise",
"temp_max": "Température maximale permise",
@@ -49,7 +50,9 @@
"used_by_controls_central_boiler": "Utilisé par la chaudière centrale. Cochez si ce VTherm doit contrôler la chaudière centrale."
},
"data_description": {
"external_temperature_sensor_entity_id": "Entity id du capteur de température extérieure."
"temperature_sensor_entity_id": "Id d'entité du capteur de température",
"last_seen_temperature_sensor_entity_id": "Id d'entité du capteur donnant la date et heure de dernière vue capteur de température. L'état doit être au format date heure (ex: 2024-03-31T17:07:03+00:00)",
"external_temperature_sensor_entity_id": "Entity id du capteur de température extérieure. Non utilisé si une configuration centrale est définie"
}
},
"features": {
@@ -281,19 +284,22 @@
"data": {
"name": "Nom",
"thermostat_type": "Type de thermostat",
"temperature_sensor_entity_id": "Température sensor entity id",
"external_temperature_sensor_entity_id": "Température exterieure sensor entity id",
"temperature_sensor_entity_id": "Capteur de température",
"last_seen_temperature_sensor_entity_id": "Dernière vue capteur de température",
"external_temperature_sensor_entity_id": "Capteur de température exterieure",
"cycle_min": "Durée du cycle (minutes)",
"temp_min": "Température minimale permise",
"temp_max": "Température maximale permise",
"step_temperature": "Pas de température",
"device_power": "Puissance de l'équipement",
"use_central_mode": "Autoriser le controle par une entity centrale ('nécessite une config. centrale`). Cochez pour autoriser le contrôle du VTherm par la liste déroulante 'central_mode' de l'entité configuration centrale.",
"use_main_central_config": "Utiliser la configuration centrale supplémentaire. Cochez pour utiliser la configuration centrale supplémentaire (température externe, min, max, pas, ...).",
"use_main_central_config": "Utiliser la configuration centrale supplémentaire. Cochez pour utiliser la configuration centrale supplémentaire (température externe, min, max, pas, ...)",
"used_by_controls_central_boiler": "Utilisé par la chaudière centrale. Cochez si ce VTherm doit contrôler la chaudière centrale."
},
"data_description": {
"external_temperature_sensor_entity_id": "Entity id du capteur de température extérieure. N'est pas utilisé si la configuration centrale est utilisée."
"temperature_sensor_entity_id": "Id d'entité du capteur de température",
"last_seen_temperature_sensor_entity_id": "Id d'entité du capteur donnant la date et heure de dernière vue capteur de température. L'état doit être au format date heure (ex: 2024-03-31T17:07:03+00:00)",
"external_temperature_sensor_entity_id": "Entity id du capteur de température extérieure. Non utilisé si une configuration centrale est définie"
}
},
"features": {
@@ -861,7 +861,7 @@ class UnderlyingValve(UnderlyingEntity):
min_val = valve_state.attributes["min"]
max_val = valve_state.attributes["max"]
new_value = round(max(min_val, min(value, max_val)))
new_value = round(max(min_val, min(value / 100 * max_val, max_val)))
else:
_LOGGER.debug("%s - no min and max attributes on underlying", self)
new_value = value
+25
View File
@@ -580,6 +580,31 @@ async def send_temperature_change_event(
return dearm_window_auto
async def send_last_seen_temperature_change_event(
entity: BaseThermostat, date, sleep=True
):
"""Sending a new last seen event simulating a change on last seen temperature sensor"""
_LOGGER.info(
"------- Testu: sending send_last_seen_temperature_change_event, date=%s on %s",
date,
entity,
)
last_seen_event = Event(
EVENT_STATE_CHANGED,
{
"new_state": State(
entity_id=entity.entity_id,
state=date,
last_changed=date,
last_updated=date,
)
},
)
await entity._async_last_seen_temperature_changed(last_seen_event)
if sleep:
await asyncio.sleep(0.1)
async def send_ext_temperature_change_event(
entity: BaseThermostat, new_temp, date, sleep=True
):
+139
View File
@@ -0,0 +1,139 @@
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long
""" Test the Security featrure """
from unittest.mock import patch, call
from datetime import timedelta, datetime
import logging
from custom_components.versatile_thermostat.thermostat_climate import (
ThermostatOverClimate,
)
from custom_components.versatile_thermostat.thermostat_switch import (
ThermostatOverSwitch,
)
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
logging.getLogger().setLevel(logging.DEBUG)
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_last_seen_feature(hass: HomeAssistant, skip_hass_states_is_state):
"""Test the last_ssen feature
1. creates a thermostat and check that security is off
2. activate security feature when date is expired
3. change the last seen sensor
4. check that security is off
"""
tz = get_tz(hass) # pylint: disable=invalid-name
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverSwitchMockName",
unique_id="uniqueId",
data={
"name": "TheOverSwitchMockName",
"thermostat_type": "thermostat_over_switch",
"temperature_sensor_entity_id": "sensor.mock_temp_sensor",
"last_seen_temperature_sensor_entity_id": "sensor.mock_last_seen_temp_sensor",
"external_temperature_sensor_entity_id": "sensor.mock_ext_temp_sensor",
"cycle_min": 5,
"temp_min": 15,
"temp_max": 30,
"frost_temp": 7,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 19,
"use_window_feature": False,
"use_motion_feature": False,
"use_power_feature": False,
"use_presence_feature": False,
"heater_entity_id": "switch.mock_switch",
"proportional_function": "tpi",
"tpi_coef_int": 0.3,
"tpi_coef_ext": 0.01,
"minimal_activation_delay": 30,
"security_delay_min": 5, # 5 minutes
"security_min_on_percent": 0.2,
"security_default_on_percent": 0.1,
},
)
# 1. creates a thermostat and check that security is off
now: datetime = datetime.now(tz=tz)
entity: ThermostatOverSwitch = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
assert entity._security_state is False
assert entity.preset_mode is not PRESET_SECURITY
assert entity._last_ext_temperature_measure is not None
assert entity._last_temperature_measure is not None
assert (entity._last_temperature_measure.astimezone(tz) - now).total_seconds() < 1
assert (
entity._last_ext_temperature_measure.astimezone(tz) - now
).total_seconds() < 1
# set a preset
assert entity.preset_mode is PRESET_NONE
await entity.async_set_preset_mode(PRESET_COMFORT)
assert entity.preset_mode is PRESET_COMFORT
# Turn On the thermostat
assert entity.hvac_mode == HVACMode.OFF
await entity.async_set_hvac_mode(HVACMode.HEAT)
assert entity.hvac_mode == HVACMode.HEAT
# 2. activate security feature when date is expired
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
) as mock_heater_on:
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
assert entity.preset_mode == PRESET_SECURITY
assert mock_send_event.call_count == 3
mock_send_event.assert_has_calls(
[
call.send_event(EventType.PRESET_EVENT, {"preset": PRESET_SECURITY}),
call.send_event(
EventType.TEMPERATURE_EVENT,
{
"last_temperature_measure": event_timestamp.isoformat(),
"last_ext_temperature_measure": entity._last_ext_temperature_measure.isoformat(),
"current_temp": 15,
"current_ext_temp": None,
"target_temp": 18,
},
),
call.send_event(
EventType.SECURITY_EVENT,
{
"type": "start",
"last_temperature_measure": event_timestamp.isoformat(),
"last_ext_temperature_measure": entity._last_ext_temperature_measure.isoformat(),
"current_temp": 15,
"current_ext_temp": None,
"target_temp": 18,
},
),
],
any_order=True,
)
assert mock_heater_on.call_count == 1
# 3. change the last seen sensor
event_timestamp = now - timedelta(minutes=4)
await send_last_seen_temperature_change_event(entity, event_timestamp)
assert entity.security_state is False
assert entity.preset_mode is PRESET_COMFORT
assert entity._last_temperature_measure == event_timestamp
+10 -18
View File
@@ -74,22 +74,12 @@ async def test_over_valve_full_start(
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
# 1. create the entity
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
assert entry.state is ConfigEntryState.LOADED
def find_my_entity(entity_id) -> ClimateEntity:
"""Find my new entity"""
component: EntityComponent[ClimateEntity] = hass.data[CLIMATE_DOMAIN]
for entity in component.entities:
if entity.entity_id == entity_id:
return entity
# The name is in the CONF and not the title of the entry
entity: ThermostatOverValve = find_my_entity("climate.theovervalvemockname")
entity = await create_thermostat(hass, entry, "climate.theovervalvemockname")
assert entity
assert isinstance(entity, ThermostatOverValve)
@@ -130,7 +120,7 @@ async def test_over_valve_full_start(
]
)
# Set the HVACMode to HEAT, with manual preset and target_temp to 18 before receiving temperature
# 2. Set the HVACMode to HEAT, with manual preset and target_temp to 18 before receiving temperature
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
@@ -158,7 +148,7 @@ async def test_over_valve_full_start(
# Nothing have changed cause we don't have room and external temperature
assert mock_send_event.call_count == 1
# Set temperature and external temperature
# 3. Set temperature and external temperature
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
@@ -220,7 +210,7 @@ async def test_over_valve_full_start(
assert entity.is_device_active is True
assert entity.hvac_action == HVACAction.HEATING
# Change internal temperature
# 4. Change internal temperature
expected_state = State(
entity_id="number.mock_valve", state="0", attributes={"min": 10, "max": 50}
)
@@ -267,7 +257,9 @@ async def test_over_valve_full_start(
call.async_call(
domain="number",
service="set_value",
service_data={"value": 50}, # the min allowed value
service_data={
"value": 34
}, # 34 is 50 x open_percent (69%) and is the max allowed value
target={"entity_id": "number.mock_valve"},
),
]
@@ -287,9 +279,9 @@ async def test_over_valve_full_start(
assert entity.is_device_active is True
assert entity.hvac_action == HVACAction.HEATING
# Test window open/close (with a normal min/max so that is_device_active is False when open_percent is 0)
# 5. Test window open/close (with a normal min/max so that is_device_active is False when open_percent is 0)
expected_state = State(
entity_id="number.mock_valve", state="0", attributes={"min": 0, "max": 99}
entity_id="number.mock_valve", state="0", attributes={"min": 0, "max": 255}
)
with patch(