Compare commits

..

1 Commits

Author SHA1 Message Date
Jean-Marc Collin
9b26d40cec Issue #683 - Window action Anti frost doesnt restore the previous temp 2024-12-08 18:43:05 +00:00
14 changed files with 55 additions and 448 deletions

View File

@@ -82,10 +82,6 @@ T = TypeVar("T", bound=UnderlyingEntity)
class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]): class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
"""Representation of a base class for all Versatile Thermostat device.""" """Representation of a base class for all Versatile Thermostat device."""
# breaking change with 2024.12 climate workaround
_attr_swing_horizontal_modes = []
_attr_swing_horizontal_mode = ""
_entity_component_unrecorded_attributes = ( _entity_component_unrecorded_attributes = (
ClimateEntity._entity_component_unrecorded_attributes.union( ClimateEntity._entity_component_unrecorded_attributes.union(
frozenset( frozenset(
@@ -131,7 +127,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
"max_power_sensor_entity_id", "max_power_sensor_entity_id",
"temperature_unit", "temperature_unit",
"is_device_active", "is_device_active",
"device_actives", "nb_device_actives",
"target_temperature_step", "target_temperature_step",
"is_used_by_central_boiler", "is_used_by_central_boiler",
"temperature_slope", "temperature_slope",
@@ -1000,19 +996,14 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
return True return True
return False return False
@property
def device_actives(self) -> int:
"""Calculate the active devices"""
ret = []
for under in self._underlyings:
if under.is_device_active:
ret.append(under.entity_id)
return ret
@property @property
def nb_device_actives(self) -> int: def nb_device_actives(self) -> int:
"""Calculate the number of active devices""" """Calculate the number of active devices"""
return len(self.device_actives) ret = 0
for under in self._underlyings:
if under.is_device_active:
ret += 1
return ret
@property @property
def current_temperature(self) -> float | None: def current_temperature(self) -> float | None:
@@ -1338,8 +1329,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
self._attr_preset_mode = PRESET_ACTIVITY self._attr_preset_mode = PRESET_ACTIVITY
await self._async_update_motion_temp() await self._async_update_motion_temp()
else: else:
if self._attr_preset_mode == PRESET_NONE: # if self._attr_preset_mode == PRESET_NONE:
self._saved_target_temp = self._target_temp # self._saved_target_temp = self._target_temp
self._attr_preset_mode = preset_mode self._attr_preset_mode = preset_mode
await self._async_internal_set_temperature( await self._async_internal_set_temperature(
self.find_preset_temp(preset_mode) self.find_preset_temp(preset_mode)
@@ -2685,7 +2676,6 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
"timezone": str(self._current_tz), "timezone": str(self._current_tz),
"temperature_unit": self.temperature_unit, "temperature_unit": self.temperature_unit,
"is_device_active": self.is_device_active, "is_device_active": self.is_device_active,
"device_actives": self.device_actives,
"nb_device_actives": self.nb_device_actives, "nb_device_actives": self.nb_device_actives,
"ema_temp": self._ema_temp, "ema_temp": self._ema_temp,
"is_used_by_central_boiler": self.is_used_by_central_boiler, "is_used_by_central_boiler": self.is_used_by_central_boiler,

View File

@@ -118,7 +118,7 @@ async def async_setup_entry(
SERVICE_SET_AUTO_REGULATION_MODE, SERVICE_SET_AUTO_REGULATION_MODE,
{ {
vol.Required("auto_regulation_mode"): vol.In( vol.Required("auto_regulation_mode"): vol.In(
["None", "Light", "Medium", "Strong", "Slow", "Expert"] ["None", "Light", "Medium", "Strong", "Slow"]
), ),
}, },
"service_set_auto_regulation_mode", "service_set_auto_regulation_mode",

View File

@@ -14,6 +14,6 @@
"quality_scale": "silver", "quality_scale": "silver",
"requirements": [], "requirements": [],
"ssdp": [], "ssdp": [],
"version": "6.8.3", "version": "6.8.0",
"zeroconf": [] "zeroconf": []
} }

View File

@@ -644,10 +644,6 @@ class NbActiveDeviceForBoilerSensor(SensorEntity):
"""Representation of the threshold of the number of VTherm """Representation of the threshold of the number of VTherm
which should be active to activate the boiler""" which should be active to activate the boiler"""
_entity_component_unrecorded_attributes = SensorEntity._entity_component_unrecorded_attributes.union( # pylint: disable=protected-access
frozenset({"active_device_ids"})
)
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None: def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
"""Initialize the energy sensor""" """Initialize the energy sensor"""
self._hass = hass self._hass = hass
@@ -657,14 +653,6 @@ class NbActiveDeviceForBoilerSensor(SensorEntity):
self._attr_unique_id = "nb_device_active_boiler" self._attr_unique_id = "nb_device_active_boiler"
self._attr_value = self._attr_native_value = None # default value self._attr_value = self._attr_native_value = None # default value
self._entities = [] self._entities = []
self._attr_active_device_ids = [] # Holds the entity ids of active devices
@property
def extra_state_attributes(self) -> dict:
"""Return additional attributes for the sensor."""
return {
"active_device_ids": self._attr_active_device_ids,
}
@property @property
def icon(self) -> str | None: def icon(self) -> str | None:
@@ -730,19 +718,19 @@ class NbActiveDeviceForBoilerSensor(SensorEntity):
self.calculate_nb_active_devices, self.calculate_nb_active_devices,
) )
_LOGGER.info( _LOGGER.info(
"%s - the underlyings that could control the central boiler are %s", "%s - the underlyings that could controls the central boiler are %s",
self, self,
underlying_entities_id, underlying_entities_id,
) )
self.async_on_remove(listener_cancel) self.async_on_remove(listener_cancel)
else: else:
_LOGGER.debug("%s - no VTherm could control the central boiler", self) _LOGGER.debug("%s - no VTherm could controls the central boiler", self)
await self.calculate_nb_active_devices(None) await self.calculate_nb_active_devices(None)
async def calculate_nb_active_devices(self, event: Event): async def calculate_nb_active_devices(self, event: Event):
"""Calculate the number of active VTherm that have an """Calculate the number of active VTherm that have an
influence on the central boiler and update the list of active device names.""" influence on central boiler"""
# _LOGGER.debug("%s- calculate_nb_active_devices - the event is %s ", self, event) # _LOGGER.debug("%s- calculate_nb_active_devices - the event is %s ", self, event)
@@ -769,8 +757,6 @@ class NbActiveDeviceForBoilerSensor(SensorEntity):
old_state is not None old_state is not None
and new_state.state == old_state.state and new_state.state == old_state.state
and new_hvac_action == old_hvac_action and new_hvac_action == old_hvac_action
# issue 698 - force recalculation when underlying climate doesn't have any hvac_action
and new_hvac_action is not None
): ):
# A false state change # A false state change
return return
@@ -788,28 +774,20 @@ class NbActiveDeviceForBoilerSensor(SensorEntity):
) )
nb_active = 0 nb_active = 0
active_device_ids = []
for entity in self._entities: for entity in self._entities:
device_actives = entity.device_actives nb_active += entity.nb_device_actives
_LOGGER.debug( _LOGGER.debug(
"After examining the hvac_action of %s, device_actives is %s", "After examining the hvac_action of %s, nb_active is %s",
entity.name, entity.name,
device_actives, nb_active,
) )
nb_active += len(device_actives)
active_device_ids.extend(device_actives)
self._attr_native_value = nb_active self._attr_native_value = nb_active
self._attr_active_device_ids = active_device_ids _LOGGER.debug(
"%s - Number of active underlying entities is %s", self, nb_active
)
self.async_write_ha_state() self.async_write_ha_state()
@property
def active_device_ids(self) -> list:
"""Get the list of active device id"""
return self._attr_active_device_ids
def __str__(self): def __str__(self):
return f"VersatileThermostat-{self.name}" return f"VersatileThermostat-{self.name}"

View File

@@ -183,8 +183,8 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
await super()._async_internal_set_temperature(temperature) await super()._async_internal_set_temperature(temperature)
self._regulation_algo.set_target_temp(self.target_temperature) self._regulation_algo.set_target_temp(self.target_temperature)
# Is necessary cause control_heating method will not force the update. # is done by control_heating method. No need to do it here
await self._send_regulated_temperature(force=True) # await self._send_regulated_temperature(force=True)
async def _send_regulated_temperature(self, force=False): async def _send_regulated_temperature(self, force=False):
"""Sends the regulated temperature to all underlying""" """Sends the regulated temperature to all underlying"""

View File

@@ -277,15 +277,12 @@ class ThermostatOverClimateValve(ThermostatOverClimate):
return self.valve_open_percent > 0 return self.valve_open_percent > 0
@property @property
def device_actives(self) -> int: def nb_device_actives(self) -> int:
"""Calculate the number of active devices""" """Calculate the number of active devices"""
if self.is_device_active: if self.is_device_active:
return [ return len(self._underlyings_valve_regulation)
under.opening_degree_entity_id
for under in self._underlyings_valve_regulation
]
else: else:
return [] return 0
@property @property
def activable_underlying_entities(self) -> list | None: def activable_underlying_entities(self) -> list | None:

View File

@@ -218,7 +218,7 @@
} }
}, },
"valve_regulation": { "valve_regulation": {
"title": "Auto-régulation par vanne", "title": "Auto-régulation par vanne - {name}",
"description": "Configuration de l'auto-régulation par controle direct de la vanne", "description": "Configuration de l'auto-régulation par controle direct de la vanne",
"data": { "data": {
"offset_calibration_entity_ids": "Entités de 'calibrage du décalage''", "offset_calibration_entity_ids": "Entités de 'calibrage du décalage''",
@@ -257,7 +257,7 @@
} }
}, },
"menu": { "menu": {
"title": "Menu", "title": "Menu - {name}",
"description": "Paramétrez votre thermostat. Vous pourrez finaliser la configuration quand tous les paramètres auront été saisis.", "description": "Paramétrez votre thermostat. Vous pourrez finaliser la configuration quand tous les paramètres auront été saisis.",
"menu_options": { "menu_options": {
"main": "Principaux Attributs", "main": "Principaux Attributs",

View File

@@ -3,5 +3,5 @@
"content_in_root": false, "content_in_root": false,
"render_readme": true, "render_readme": true,
"hide_default_branch": false, "hide_default_branch": false,
"homeassistant": "2024.12.3" "homeassistant": "2024.10.4"
} }

View File

@@ -1 +1 @@
homeassistant==2024.12.3 homeassistant==2024.10.4

View File

@@ -579,7 +579,6 @@ class MockNumber(NumberEntity):
def set_native_value(self, value: float): def set_native_value(self, value: float):
"""Change the value""" """Change the value"""
self._attr_native_value = value self._attr_native_value = value
self.async_write_ha_state()
async def create_thermostat( async def create_thermostat(

View File

@@ -309,7 +309,7 @@ async def test_over_climate_regulation_limitations(
assert entity.hvac_action == HVACAction.HEATING assert entity.hvac_action == HVACAction.HEATING
# the regulated temperature will not change because when we set temp manually it is forced # the regulated temperature will not change because when we set temp manually it is forced
assert entity.regulated_target_temp == 19.5 assert entity.regulated_target_temp == 17 # 19.5
# 2. set manual target temp (at now - 18) -> the regulation should be taken into account # 2. set manual target temp (at now - 18) -> the regulation should be taken into account
event_timestamp = now - timedelta(minutes=18) event_timestamp = now - timedelta(minutes=18)

View File

@@ -2,7 +2,7 @@
""" Test the central_configuration """ """ Test the central_configuration """
import asyncio import asyncio
from datetime import datetime, timedelta from datetime import datetime
from unittest.mock import patch, call from unittest.mock import patch, call
@@ -29,8 +29,6 @@ from custom_components.versatile_thermostat.binary_sensor import (
CentralBoilerBinarySensor, CentralBoilerBinarySensor,
) )
from custom_components.versatile_thermostat.sensor import NbActiveDeviceForBoilerSensor
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
@@ -105,7 +103,7 @@ async def test_update_central_boiler_state_simple(
CONF_USE_MOTION_FEATURE: False, CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False, CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False, CONF_USE_PRESENCE_FEATURE: False,
CONF_UNDERLYING_LIST: [switch1.entity_id], CONF_HEATER: switch1.entity_id,
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_INVERSE_SWITCH: False, CONF_INVERSE_SWITCH: False,
CONF_TPI_COEF_INT: 0.3, CONF_TPI_COEF_INT: 0.3,
@@ -149,13 +147,6 @@ async def test_update_central_boiler_state_simple(
assert boiler_binary_sensor is not None assert boiler_binary_sensor is not None
assert boiler_binary_sensor.state == STATE_OFF assert boiler_binary_sensor.state == STATE_OFF
nb_device_active_sensor: NbActiveDeviceForBoilerSensor = search_entity(
hass, "sensor.nb_device_active_for_boiler", "sensor"
)
assert nb_device_active_sensor is not None
assert nb_device_active_sensor.state == 0
assert nb_device_active_sensor.active_device_ids == []
# 1. start a heater # 1. start a heater
with patch( with patch(
"homeassistant.core.ServiceRegistry.async_call" "homeassistant.core.ServiceRegistry.async_call"
@@ -204,9 +195,6 @@ async def test_update_central_boiler_state_simple(
assert api.nb_active_device_for_boiler == 1 assert api.nb_active_device_for_boiler == 1
assert boiler_binary_sensor.state == STATE_ON assert boiler_binary_sensor.state == STATE_ON
assert nb_device_active_sensor.state == 1
assert nb_device_active_sensor.active_device_ids == ["switch.switch1"]
# 2. stop a heater # 2. stop a heater
with patch( with patch(
"homeassistant.core.ServiceRegistry.async_call" "homeassistant.core.ServiceRegistry.async_call"
@@ -247,9 +235,6 @@ async def test_update_central_boiler_state_simple(
assert api.nb_active_device_for_boiler == 0 assert api.nb_active_device_for_boiler == 0
assert boiler_binary_sensor.state == STATE_OFF assert boiler_binary_sensor.state == STATE_OFF
assert nb_device_active_sensor.state == 0
assert nb_device_active_sensor.active_device_ids == []
entity.remove_thermostat() entity.remove_thermostat()
@@ -287,12 +272,10 @@ async def test_update_central_boiler_state_multiple(
CONF_USE_MOTION_FEATURE: False, CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False, CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False, CONF_USE_PRESENCE_FEATURE: False,
CONF_UNDERLYING_LIST: [ CONF_HEATER: switch1.entity_id,
switch1.entity_id, CONF_HEATER_2: switch2.entity_id,
switch2.entity_id, CONF_HEATER_3: switch3.entity_id,
switch3.entity_id, CONF_HEATER_4: switch4.entity_id,
switch4.entity_id,
],
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_INVERSE_SWITCH: False, CONF_INVERSE_SWITCH: False,
CONF_TPI_COEF_INT: 0.3, CONF_TPI_COEF_INT: 0.3,
@@ -319,18 +302,10 @@ async def test_update_central_boiler_state_multiple(
assert entity.underlying_entities[1].entity_id == "switch.switch2" assert entity.underlying_entities[1].entity_id == "switch.switch2"
assert entity.underlying_entities[2].entity_id == "switch.switch3" assert entity.underlying_entities[2].entity_id == "switch.switch3"
assert entity.underlying_entities[3].entity_id == "switch.switch4" assert entity.underlying_entities[3].entity_id == "switch.switch4"
assert entity.device_actives == [] assert entity.nb_device_actives == 0
assert api.nb_active_device_for_boiler_threshold == 1 assert api.nb_active_device_for_boiler_threshold == 1
assert api.nb_active_device_for_boiler == 0 assert api.nb_active_device_for_boiler == 0
nb_device_active_sensor: NbActiveDeviceForBoilerSensor = search_entity(
hass, "sensor.nb_device_active_for_boiler", "sensor"
)
assert nb_device_active_sensor is not None
assert nb_device_active_sensor.state == 0
assert nb_device_active_sensor.active_device_ids == []
# Force the VTherm to heat # Force the VTherm to heat
await entity.async_set_hvac_mode(HVACMode.HEAT) await entity.async_set_hvac_mode(HVACMode.HEAT)
await entity.async_set_preset_mode(PRESET_BOOST) await entity.async_set_preset_mode(PRESET_BOOST)
@@ -363,7 +338,7 @@ async def test_update_central_boiler_state_multiple(
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
assert entity.hvac_action == HVACAction.HEATING assert entity.hvac_action == HVACAction.HEATING
assert entity.device_actives == ["switch.switch1"] assert entity.nb_device_actives == 1
assert mock_service_call.call_count == 1 assert mock_service_call.call_count == 1
# No switch of the boiler # No switch of the boiler
@@ -381,9 +356,6 @@ async def test_update_central_boiler_state_multiple(
assert api.nb_active_device_for_boiler == 1 assert api.nb_active_device_for_boiler == 1
assert boiler_binary_sensor.state == STATE_OFF assert boiler_binary_sensor.state == STATE_OFF
assert nb_device_active_sensor.state == 1
assert nb_device_active_sensor.active_device_ids == ["switch.switch1"]
# 2. start a 2nd heater # 2. start a 2nd heater
with patch( with patch(
"homeassistant.core.ServiceRegistry.async_call" "homeassistant.core.ServiceRegistry.async_call"
@@ -396,7 +368,7 @@ async def test_update_central_boiler_state_multiple(
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
assert entity.hvac_action == HVACAction.HEATING assert entity.hvac_action == HVACAction.HEATING
assert entity.device_actives == ["switch.switch1", "switch.switch2"] assert entity.nb_device_actives == 2
# Only the first heater is started by the algo # Only the first heater is started by the algo
assert mock_service_call.call_count == 1 assert mock_service_call.call_count == 1
@@ -416,12 +388,6 @@ async def test_update_central_boiler_state_multiple(
assert api.nb_active_device_for_boiler == 2 assert api.nb_active_device_for_boiler == 2
assert boiler_binary_sensor.state == STATE_OFF assert boiler_binary_sensor.state == STATE_OFF
assert nb_device_active_sensor.state == 2
assert nb_device_active_sensor.active_device_ids == [
"switch.switch1",
"switch.switch2",
]
# 3. start a 3rd heater # 3. start a 3rd heater
with patch( with patch(
"homeassistant.core.ServiceRegistry.async_call" "homeassistant.core.ServiceRegistry.async_call"
@@ -470,13 +436,6 @@ async def test_update_central_boiler_state_multiple(
assert api.nb_active_device_for_boiler == 3 assert api.nb_active_device_for_boiler == 3
assert boiler_binary_sensor.state == STATE_ON assert boiler_binary_sensor.state == STATE_ON
assert nb_device_active_sensor.state == 3
assert nb_device_active_sensor.active_device_ids == [
"switch.switch1",
"switch.switch2",
"switch.switch3",
]
# 4. start a 4th heater # 4. start a 4th heater
with patch( with patch(
"homeassistant.core.ServiceRegistry.async_call" "homeassistant.core.ServiceRegistry.async_call"
@@ -507,14 +466,6 @@ async def test_update_central_boiler_state_multiple(
assert api.nb_active_device_for_boiler == 4 assert api.nb_active_device_for_boiler == 4
assert boiler_binary_sensor.state == STATE_ON assert boiler_binary_sensor.state == STATE_ON
assert nb_device_active_sensor.state == 4
assert nb_device_active_sensor.active_device_ids == [
"switch.switch1",
"switch.switch2",
"switch.switch3",
"switch.switch4",
]
# 5. stop a heater # 5. stop a heater
with patch( with patch(
"homeassistant.core.ServiceRegistry.async_call" "homeassistant.core.ServiceRegistry.async_call"
@@ -533,13 +484,6 @@ async def test_update_central_boiler_state_multiple(
assert api.nb_active_device_for_boiler == 3 assert api.nb_active_device_for_boiler == 3
assert boiler_binary_sensor.state == STATE_ON assert boiler_binary_sensor.state == STATE_ON
assert nb_device_active_sensor.state == 3
assert nb_device_active_sensor.active_device_ids == [
"switch.switch2",
"switch.switch3",
"switch.switch4",
]
# 6. stop a 2nd heater # 6. stop a 2nd heater
with patch( with patch(
"homeassistant.core.ServiceRegistry.async_call" "homeassistant.core.ServiceRegistry.async_call"
@@ -580,12 +524,6 @@ async def test_update_central_boiler_state_multiple(
assert api.nb_active_device_for_boiler == 2 assert api.nb_active_device_for_boiler == 2
assert boiler_binary_sensor.state == STATE_OFF assert boiler_binary_sensor.state == STATE_OFF
assert nb_device_active_sensor.state == 2
assert nb_device_active_sensor.active_device_ids == [
"switch.switch2",
"switch.switch3",
]
entity.remove_thermostat() entity.remove_thermostat()
@@ -620,7 +558,7 @@ async def test_update_central_boiler_state_simple_valve(
CONF_USE_MOTION_FEATURE: False, CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False, CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False, CONF_USE_PRESENCE_FEATURE: False,
CONF_UNDERLYING_LIST: [valve1.entity_id], CONF_VALVE: valve1.entity_id,
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_INVERSE_SWITCH: False, CONF_INVERSE_SWITCH: False,
CONF_TPI_COEF_INT: 0.3, CONF_TPI_COEF_INT: 0.3,
@@ -656,7 +594,7 @@ async def test_update_central_boiler_state_simple_valve(
now: datetime = datetime.now(tz=tz) now: datetime = datetime.now(tz=tz)
assert entity.hvac_mode == HVACMode.HEAT assert entity.hvac_mode == HVACMode.HEAT
assert entity.device_actives == [] assert entity.nb_device_actives == 0
boiler_binary_sensor: CentralBoilerBinarySensor = search_entity( boiler_binary_sensor: CentralBoilerBinarySensor = search_entity(
hass, "binary_sensor.central_boiler", "binary_sensor" hass, "binary_sensor.central_boiler", "binary_sensor"
@@ -664,13 +602,6 @@ async def test_update_central_boiler_state_simple_valve(
assert boiler_binary_sensor is not None assert boiler_binary_sensor is not None
assert boiler_binary_sensor.state == STATE_OFF assert boiler_binary_sensor.state == STATE_OFF
nb_device_active_sensor: NbActiveDeviceForBoilerSensor = search_entity(
hass, "sensor.nb_device_active_for_boiler", "sensor"
)
assert nb_device_active_sensor is not None
assert nb_device_active_sensor.state == 0
assert nb_device_active_sensor.active_device_ids == []
# 1. start a valve # 1. start a valve
with patch( with patch(
"homeassistant.core.ServiceRegistry.async_call" "homeassistant.core.ServiceRegistry.async_call"
@@ -685,7 +616,7 @@ async def test_update_central_boiler_state_simple_valve(
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
assert entity.hvac_action == HVACAction.HEATING assert entity.hvac_action == HVACAction.HEATING
assert entity.device_actives == ["number.valve1"] assert entity.nb_device_actives == 1
assert mock_service_call.call_count >= 1 assert mock_service_call.call_count >= 1
mock_service_call.assert_has_calls( mock_service_call.assert_has_calls(
@@ -713,11 +644,6 @@ async def test_update_central_boiler_state_simple_valve(
assert api.nb_active_device_for_boiler == 1 assert api.nb_active_device_for_boiler == 1
assert boiler_binary_sensor.state == STATE_ON assert boiler_binary_sensor.state == STATE_ON
assert nb_device_active_sensor.state == 1
assert nb_device_active_sensor.active_device_ids == [
"number.valve1",
]
# 2. stop a heater # 2. stop a heater
with patch( with patch(
"homeassistant.core.ServiceRegistry.async_call" "homeassistant.core.ServiceRegistry.async_call"
@@ -732,7 +658,7 @@ async def test_update_central_boiler_state_simple_valve(
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
assert entity.hvac_action == HVACAction.IDLE assert entity.hvac_action == HVACAction.IDLE
assert entity.device_actives == [] assert entity.nb_device_actives == 0
assert mock_service_call.call_count >= 1 assert mock_service_call.call_count >= 1
mock_service_call.assert_has_calls( mock_service_call.assert_has_calls(
@@ -761,9 +687,6 @@ async def test_update_central_boiler_state_simple_valve(
assert api.nb_active_device_for_boiler == 0 assert api.nb_active_device_for_boiler == 0
assert boiler_binary_sensor.state == STATE_OFF assert boiler_binary_sensor.state == STATE_OFF
assert nb_device_active_sensor.state == 0
assert nb_device_active_sensor.active_device_ids == []
entity.remove_thermostat() entity.remove_thermostat()
@@ -798,7 +721,7 @@ async def test_update_central_boiler_state_simple_climate(
CONF_USE_MOTION_FEATURE: False, CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False, CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False, CONF_USE_PRESENCE_FEATURE: False,
CONF_UNDERLYING_LIST: [climate1.entity_id], CONF_CLIMATE: climate1.entity_id,
CONF_MINIMAL_ACTIVATION_DELAY: 30, CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5, CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3, CONF_SECURITY_MIN_ON_PERCENT: 0.3,
@@ -825,13 +748,6 @@ async def test_update_central_boiler_state_simple_climate(
assert api.nb_active_device_for_boiler_threshold == 1 assert api.nb_active_device_for_boiler_threshold == 1
assert api.nb_active_device_for_boiler == 0 assert api.nb_active_device_for_boiler == 0
nb_device_active_sensor: NbActiveDeviceForBoilerSensor = search_entity(
hass, "sensor.nb_device_active_for_boiler", "sensor"
)
assert nb_device_active_sensor is not None
assert nb_device_active_sensor.state == 0
assert nb_device_active_sensor.active_device_ids == []
# Force the VTherm to heat # Force the VTherm to heat
await entity.async_set_hvac_mode(HVACMode.HEAT) await entity.async_set_hvac_mode(HVACMode.HEAT)
await entity.async_set_preset_mode(PRESET_BOOST) await entity.async_set_preset_mode(PRESET_BOOST)
@@ -840,7 +756,7 @@ async def test_update_central_boiler_state_simple_climate(
now: datetime = datetime.now(tz=tz) now: datetime = datetime.now(tz=tz)
assert entity.hvac_mode == HVACMode.HEAT assert entity.hvac_mode == HVACMode.HEAT
assert entity.device_actives == [] assert entity.nb_device_actives == 0
boiler_binary_sensor: CentralBoilerBinarySensor = search_entity( boiler_binary_sensor: CentralBoilerBinarySensor = search_entity(
hass, "binary_sensor.central_boiler", "binary_sensor" hass, "binary_sensor.central_boiler", "binary_sensor"
@@ -863,7 +779,7 @@ async def test_update_central_boiler_state_simple_climate(
await asyncio.sleep(0.5) await asyncio.sleep(0.5)
assert entity.hvac_action == HVACAction.HEATING assert entity.hvac_action == HVACAction.HEATING
assert entity.device_actives == ["climate.climate1"] assert entity.nb_device_actives == 1
assert mock_service_call.call_count >= 1 assert mock_service_call.call_count >= 1
mock_service_call.assert_has_calls( mock_service_call.assert_has_calls(
@@ -891,11 +807,6 @@ async def test_update_central_boiler_state_simple_climate(
assert api.nb_active_device_for_boiler == 1 assert api.nb_active_device_for_boiler == 1
assert boiler_binary_sensor.state == STATE_ON assert boiler_binary_sensor.state == STATE_ON
assert nb_device_active_sensor.state == 1
assert nb_device_active_sensor.active_device_ids == [
"climate.climate1",
]
# 2. stop a climate # 2. stop a climate
with patch( with patch(
"homeassistant.core.ServiceRegistry.async_call" "homeassistant.core.ServiceRegistry.async_call"
@@ -910,7 +821,7 @@ async def test_update_central_boiler_state_simple_climate(
await asyncio.sleep(0.5) await asyncio.sleep(0.5)
assert entity.hvac_action == HVACAction.IDLE assert entity.hvac_action == HVACAction.IDLE
assert entity.device_actives == [] assert entity.nb_device_actives == 0
assert mock_service_call.call_count >= 1 assert mock_service_call.call_count >= 1
mock_service_call.assert_has_calls( mock_service_call.assert_has_calls(
@@ -939,277 +850,6 @@ async def test_update_central_boiler_state_simple_climate(
assert api.nb_active_device_for_boiler == 0 assert api.nb_active_device_for_boiler == 0
assert boiler_binary_sensor.state == STATE_OFF assert boiler_binary_sensor.state == STATE_OFF
assert nb_device_active_sensor.state == 0
assert nb_device_active_sensor.active_device_ids == []
entity.remove_thermostat()
async def test_update_central_boiler_state_simple_climate_valve_regulation(
hass: HomeAssistant,
# skip_hass_states_is_state,
# skip_hass_states_get,
init_central_config_with_boiler_fixture,
):
"""Test that the central boiler state behavior with a climate with valve regulation"""
api = VersatileThermostatAPI.get_vtherm_api(hass)
climate1 = MockClimate(hass, "climate1", "theClimate1")
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: 8,
CONF_TEMP_MAX: 18,
"frost_temp": 10,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 21,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_UNDERLYING_LIST: [climate1.entity_id],
CONF_OPENING_DEGREE_LIST: ["number.mock_opening_degree"],
CONF_CLOSING_DEGREE_LIST: [],
CONF_OFFSET_CALIBRATION_LIST: [],
CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_VALVE,
CONF_AUTO_REGULATION_DTEMP: 0,
CONF_AUTO_REGULATION_PERIOD_MIN: 0,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.1,
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_HIGH,
CONF_AUTO_REGULATION_USE_DEVICE_TEMP: False,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_PRESETS_CENTRAL_CONFIG: True,
CONF_USE_ADVANCED_CENTRAL_CONFIG: True,
CONF_USED_BY_CENTRAL_BOILER: True,
},
)
open_degree_entity = MockNumber(hass, "mock_opening_degree", "Opening degree")
open_degree_entity.set_native_value(0)
# mock_get_state will be called for each OPENING/CLOSING/OFFSET_CALIBRATION list
mock_get_state_side_effect = SideEffects(
{
open_degree_entity.entity_id: State(
open_degree_entity.entity_id,
open_degree_entity.state,
{"min": 0, "max": 100},
),
"number.mock_closing_degree": State(
"number.mock_closing_degree", "0", {"min": 0, "max": 100}
),
"number.mock_offset_calibration": State(
"number.mock_offset_calibration", "0", {"min": -12, "max": 12}
),
},
State("unknown.entity_id", "unknown"),
)
with patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
return_value=climate1,
), patch(
"homeassistant.core.StateMachine.get",
side_effect=mock_get_state_side_effect.get_side_effects(),
):
entity: ThermostatOverClimate = await create_thermostat(
hass, entry, "climate.theoverclimatemockname"
)
assert entity
assert entity.name == "TheOverClimateMockName"
assert entity.is_over_climate
assert entity.underlying_entities[0].entity_id == "climate.climate1"
assert api.nb_active_device_for_boiler_threshold == 1
assert api.nb_active_device_for_boiler == 0
nb_device_active_sensor: NbActiveDeviceForBoilerSensor = search_entity(
hass, "sensor.nb_device_active_for_boiler", "sensor"
)
assert nb_device_active_sensor is not None
assert nb_device_active_sensor.state == 0
assert nb_device_active_sensor.active_device_ids == []
# Force the VTherm to heat
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
entity._set_now(now)
await send_temperature_change_event(entity, 30, now)
await send_ext_temperature_change_event(entity, 30, now)
await hass.async_block_till_done()
await entity.async_set_hvac_mode(HVACMode.HEAT)
await entity.async_set_preset_mode(PRESET_BOOST)
# the VTherm should not heat now
assert entity.hvac_mode == HVACMode.HEAT
assert entity.hvac_action == HVACAction.OFF
assert entity.activable_underlying_entities[0]._percent_open == 0
assert entity.device_actives == []
boiler_binary_sensor: CentralBoilerBinarySensor = search_entity(
hass, "binary_sensor.central_boiler", "binary_sensor"
)
assert boiler_binary_sensor is not None
assert boiler_binary_sensor.state == STATE_OFF
# 1. start a climate
open_degree_entity.set_native_value(100)
mock_get_state_side_effect = SideEffects(
{
open_degree_entity.entity_id: State(
open_degree_entity.entity_id,
open_degree_entity.state,
{"min": 0, "max": 100},
),
"number.mock_closing_degree": State(
"number.mock_closing_degree", "0", {"min": 0, "max": 100}
),
"number.mock_offset_calibration": State(
"number.mock_offset_calibration", "0", {"min": -12, "max": 12}
),
},
State("unknown.entity_id", "unknown"),
)
with patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"custom_components.versatile_thermostat.binary_sensor.send_vtherm_event"
) as mock_send_event, patch(
"homeassistant.core.StateMachine.get",
side_effect=mock_get_state_side_effect.get_side_effects(),
):
now = now + timedelta(minutes=1)
entity._set_now(now)
await send_temperature_change_event(entity, 10, now)
# we have to simulate the climate also else the test don't work
climate1.set_hvac_mode(HVACMode.HEAT)
climate1.set_hvac_action(HVACAction.HEATING)
climate1.async_write_ha_state()
open_degree_entity.set_native_value(100)
# Wait for state event propagation
await hass.async_block_till_done()
assert entity.hvac_action == HVACAction.HEATING
assert entity.device_actives == ["number.mock_opening_degree"]
assert api.nb_active_device_for_boiler == 1
assert boiler_binary_sensor.state == STATE_ON
assert nb_device_active_sensor.state == 1
assert nb_device_active_sensor.active_device_ids == [
"number.mock_opening_degree",
]
assert mock_service_call.call_count >= 1
mock_service_call.assert_has_calls(
[
call.service_call(
"switch",
"turn_on",
service_data={},
target={"entity_id": "switch.pompe_chaudiere"},
),
]
)
assert mock_send_event.call_count >= 1
mock_send_event.assert_has_calls(
[
call.send_vtherm_event(
hass=hass,
event_type=EventType.CENTRAL_BOILER_EVENT,
entity=api.central_boiler_entity,
data={"central_boiler": True},
)
]
)
# 2. stop a climate
open_degree_entity.set_native_value(0)
mock_get_state_side_effect = SideEffects(
{
open_degree_entity.entity_id: State(
open_degree_entity.entity_id,
open_degree_entity.state,
{"min": 0, "max": 100},
),
"number.mock_closing_degree": State(
"number.mock_closing_degree", "0", {"min": 0, "max": 100}
),
"number.mock_offset_calibration": State(
"number.mock_offset_calibration", "0", {"min": -12, "max": 12}
),
},
State("unknown.entity_id", "unknown"),
)
with patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"custom_components.versatile_thermostat.binary_sensor.send_vtherm_event"
) as mock_send_event, patch(
"homeassistant.core.StateMachine.get",
side_effect=mock_get_state_side_effect.get_side_effects(),
):
await send_temperature_change_event(entity, 25, now)
climate1.set_hvac_mode(HVACMode.HEAT)
climate1.set_hvac_action(HVACAction.IDLE)
climate1.async_write_ha_state()
open_degree_entity.set_native_value(0)
# Wait for state event propagation
await asyncio.sleep(0.5)
assert entity.hvac_action == HVACAction.OFF
assert entity.device_actives == []
assert mock_service_call.call_count >= 1
mock_service_call.assert_has_calls(
[
call(
"switch",
"turn_off",
service_data={},
target={"entity_id": "switch.pompe_chaudiere"},
)
]
)
assert mock_send_event.call_count >= 1
mock_send_event.assert_has_calls(
[
call.send_vtherm_event(
hass=hass,
event_type=EventType.CENTRAL_BOILER_EVENT,
entity=api.central_boiler_entity,
data={"central_boiler": False},
)
]
)
assert api.nb_active_device_for_boiler == 0
assert boiler_binary_sensor.state == STATE_OFF
assert nb_device_active_sensor.state == 0
assert nb_device_active_sensor.active_device_ids == []
entity.remove_thermostat() entity.remove_thermostat()

View File

@@ -18,7 +18,7 @@ from .const import *
logging.getLogger().setLevel(logging.DEBUG) logging.getLogger().setLevel(logging.DEBUG)
@pytest.mark.parametrize("expected_lingering_tasks", [True]) # @pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True]) @pytest.mark.parametrize("expected_lingering_timers", [True])
# this test fails if run in // with the next because the underlying_valve_regulation is mixed. Don't know why # this test fails if run in // with the next because the underlying_valve_regulation is mixed. Don't know why
# @pytest.mark.skip # @pytest.mark.skip
@@ -138,13 +138,13 @@ async def test_over_climate_valve_mono(hass: HomeAssistant, skip_hass_states_get
assert mock_service_call.call_count == 3 assert mock_service_call.call_count == 3
mock_service_call.assert_has_calls( mock_service_call.assert_has_calls(
[ [
call(domain='number', service='set_value', service_data={'value': 0}, target={'entity_id': 'number.mock_opening_degree'}),
call(domain='number', service='set_value', service_data={'value': 100}, target={'entity_id': 'number.mock_closing_degree'}),
call("climate","set_temperature",{ call("climate","set_temperature",{
"entity_id": "climate.mock_climate", "entity_id": "climate.mock_climate",
"temperature": 15, # temp-min "temperature": 15, # temp-min
}, },
), ),
call(domain='number', service='set_value', service_data={'value': 0}, target={'entity_id': 'number.mock_opening_degree'}),
call(domain='number', service='set_value', service_data={'value': 100}, target={'entity_id': 'number.mock_closing_degree'}),
# we have no current_temperature yet # we have no current_temperature yet
# call(domain='number', service='set_value', service_data={'value': 12}, target={'entity_id': 'number.mock_offset_calibration'}), # call(domain='number', service='set_value', service_data={'value': 12}, target={'entity_id': 'number.mock_offset_calibration'}),
] ]
@@ -300,7 +300,6 @@ async def test_over_climate_valve_mono(hass: HomeAssistant, skip_hass_states_get
await hass.async_block_till_done() await hass.async_block_till_done()
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_over_climate_valve_multi_presence( async def test_over_climate_valve_multi_presence(
hass: HomeAssistant, skip_hass_states_get hass: HomeAssistant, skip_hass_states_get
): ):

View File

@@ -1802,7 +1802,7 @@ async def test_window_action_frost_temp(hass: HomeAssistant, skip_hass_states_is
event_timestamp = event_timestamp + timedelta(minutes=1) event_timestamp = event_timestamp + timedelta(minutes=1)
await send_temperature_change_event(entity, 19, event_timestamp) await send_temperature_change_event(entity, 19, event_timestamp)
# 2. Make the temperature down # 2. Make the temperature down -> no change
with patch( with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event" "custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch( ) as mock_send_event, patch(
@@ -1824,7 +1824,7 @@ async def test_window_action_frost_temp(hass: HomeAssistant, skip_hass_states_is
assert entity.window_state is STATE_OFF assert entity.window_state is STATE_OFF
assert entity.window_auto_state is STATE_OFF assert entity.window_auto_state is STATE_OFF
# 3. send one degre down in one minute # 3. send one degre down in one minute -> window is on
with patch( with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event" "custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch( ) as mock_send_event, patch(
@@ -1852,6 +1852,8 @@ async def test_window_action_frost_temp(hass: HomeAssistant, skip_hass_states_is
assert entity.preset_mode is PRESET_BOOST assert entity.preset_mode is PRESET_BOOST
# The eco temp # The eco temp
assert entity.target_temperature == 10 assert entity.target_temperature == 10
# The last temp is saved
assert entity._saved_target_temp == 21
mock_send_event.assert_has_calls( mock_send_event.assert_has_calls(
[ [
@@ -1889,6 +1891,7 @@ async def test_window_action_frost_temp(hass: HomeAssistant, skip_hass_states_is
assert entity.preset_mode is PRESET_BOOST assert entity.preset_mode is PRESET_BOOST
# The eco temp # The eco temp
assert entity.target_temperature == 10 assert entity.target_temperature == 10
assert entity._saved_target_temp == 21
# 5. send another plus 1.1 degre in one minute -> restore state # 5. send another plus 1.1 degre in one minute -> restore state
with patch( with patch(
@@ -1929,6 +1932,7 @@ async def test_window_action_frost_temp(hass: HomeAssistant, skip_hass_states_is
assert entity.preset_mode is PRESET_BOOST assert entity.preset_mode is PRESET_BOOST
# The eco temp # The eco temp
assert entity.target_temperature == 21 assert entity.target_temperature == 21
assert entity._saved_target_temp == 21
# Clean the entity # Clean the entity
entity.remove_thermostat() entity.remove_thermostat()