With tests and list of active

This commit is contained in:
Jean-Marc Collin
2024-12-21 15:39:05 +00:00
parent 9839ed4920
commit 6226d26c6d
6 changed files with 419 additions and 47 deletions

View File

@@ -131,7 +131,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",
"nb_device_actives", "device_actives",
"target_temperature_step", "target_temperature_step",
"is_used_by_central_boiler", "is_used_by_central_boiler",
"temperature_slope", "temperature_slope",
@@ -1001,14 +1001,19 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
return False return False
@property @property
def nb_device_actives(self) -> int: def device_actives(self) -> int:
"""Calculate the number of active devices""" """Calculate the active devices"""
ret = 0 ret = []
for under in self._underlyings: for under in self._underlyings:
if under.is_device_active: if under.is_device_active:
ret += 1 ret.append(under.entity_id)
return ret return ret
@property
def nb_device_actives(self) -> int:
"""Calculate the number of active devices"""
return len(self.device_actives)
@property @property
def current_temperature(self) -> float | None: def current_temperature(self) -> float | None:
"""Return the sensor temperature.""" """Return the sensor temperature."""
@@ -2680,6 +2685,7 @@ 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

@@ -644,6 +644,10 @@ 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
@@ -653,13 +657,13 @@ 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_names = [] # Holds the names of active devices self._attr_active_device_ids = [] # Holds the entity ids of active devices
@property @property
def extra_state_attributes(self) -> dict: def extra_state_attributes(self) -> dict:
"""Return additional attributes for the sensor.""" """Return additional attributes for the sensor."""
return { return {
"active_device_names": self._attr_active_device_names, "active_device_ids": self._attr_active_device_ids,
} }
@property @property
@@ -782,30 +786,28 @@ class NbActiveDeviceForBoilerSensor(SensorEntity):
) )
nb_active = 0 nb_active = 0
active_device_names = [] active_device_ids = []
for entity in self._entities: for entity in self._entities:
nb_active += entity.nb_device_actives device_actives = entity.device_actives
_LOGGER.debug( _LOGGER.debug(
"After examining the hvac_action of %s, nb_active is %s", "After examining the hvac_action of %s, device_actives is %s",
entity.name, entity.name,
nb_active, device_actives,
) )
if ( nb_active += len(device_actives)
entity.hvac_mode in [HVACMode.HEAT, HVACMode.AUTO] active_device_ids.extend(device_actives)
and entity.hvac_action == HVACAction.HEATING
):
for under in entity.underlying_entities:
if under.is_device_active:
nb_active += 1
active_device_names.append(under.entity_id)
self._attr_native_value = nb_active self._attr_native_value = nb_active
self._attr_active_device_names = active_device_names self._attr_active_device_ids = active_device_ids
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

@@ -277,12 +277,15 @@ class ThermostatOverClimateValve(ThermostatOverClimate):
return self.valve_open_percent > 0 return self.valve_open_percent > 0
@property @property
def nb_device_actives(self) -> int: def 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 len(self._underlyings_valve_regulation) return [
under.opening_degree_entity_id
for under in self._underlyings_valve_regulation
]
else: else:
return 0 return []
@property @property
def activable_underlying_entities(self) -> list | None: def activable_underlying_entities(self) -> list | None:

View File

@@ -579,6 +579,7 @@ 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

@@ -2,7 +2,7 @@
""" Test the central_configuration """ """ Test the central_configuration """
import asyncio import asyncio
from datetime import datetime from datetime import datetime, timedelta
from unittest.mock import patch, call from unittest.mock import patch, call
@@ -29,6 +29,8 @@ 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
@@ -103,7 +105,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_HEATER: switch1.entity_id, CONF_UNDERLYING_LIST: [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,
@@ -147,6 +149,13 @@ 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"
@@ -195,6 +204,9 @@ 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"
@@ -235,6 +247,9 @@ 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()
@@ -272,10 +287,12 @@ 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_HEATER: switch1.entity_id, CONF_UNDERLYING_LIST: [
CONF_HEATER_2: switch2.entity_id, switch1.entity_id,
CONF_HEATER_3: switch3.entity_id, switch2.entity_id,
CONF_HEATER_4: switch4.entity_id, switch3.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,
@@ -302,10 +319,18 @@ 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.nb_device_actives == 0 assert entity.device_actives == []
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)
@@ -338,7 +363,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.nb_device_actives == 1 assert entity.device_actives == ["switch.switch1"]
assert mock_service_call.call_count == 1 assert mock_service_call.call_count == 1
# No switch of the boiler # No switch of the boiler
@@ -356,6 +381,9 @@ 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"
@@ -368,7 +396,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.nb_device_actives == 2 assert entity.device_actives == ["switch.switch1", "switch.switch2"]
# 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
@@ -388,6 +416,12 @@ 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"
@@ -436,6 +470,13 @@ 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"
@@ -466,6 +507,14 @@ 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"
@@ -484,6 +533,13 @@ 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"
@@ -524,6 +580,12 @@ 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()
@@ -558,7 +620,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_VALVE: valve1.entity_id, CONF_UNDERLYING_LIST: [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,
@@ -594,7 +656,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.nb_device_actives == 0 assert entity.device_actives == []
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"
@@ -602,6 +664,13 @@ 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"
@@ -616,7 +685,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.nb_device_actives == 1 assert entity.device_actives == ["number.valve1"]
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(
@@ -644,6 +713,11 @@ 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"
@@ -658,7 +732,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.nb_device_actives == 0 assert entity.device_actives == []
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(
@@ -687,6 +761,9 @@ 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()
@@ -721,7 +798,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_CLIMATE: climate1.entity_id, CONF_UNDERLYING_LIST: [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,
@@ -748,6 +825,13 @@ 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)
@@ -756,7 +840,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.nb_device_actives == 0 assert entity.device_actives == []
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"
@@ -779,7 +863,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.nb_device_actives == 1 assert entity.device_actives == ["climate.climate1"]
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(
@@ -807,6 +891,11 @@ 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"
@@ -821,7 +910,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.nb_device_actives == 0 assert entity.device_actives == []
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(
@@ -850,6 +939,277 @@ 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,10 +18,10 @@ 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
async def test_over_climate_valve_mono(hass: HomeAssistant, skip_hass_states_get): async def test_over_climate_valve_mono(hass: HomeAssistant, skip_hass_states_get):
"""Test the normal full start of a thermostat in thermostat_over_climate type""" """Test the normal full start of a thermostat in thermostat_over_climate type"""
@@ -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'}),
] ]