Feature 124 add inversion pilot wire (#149)

* Add inverse switch command for ThermostatOverSwitch

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
This commit is contained in:
Jean-Marc Collin
2023-11-01 12:08:13 +01:00
committed by GitHub
parent dd7d6c97b3
commit 2ebeac30e6
16 changed files with 292 additions and 68 deletions

View File

@@ -104,6 +104,7 @@ from .const import (
CONF_AUTO_REGULATION_NONE,
CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN,
CONF_INVERSE_SWITCH,
UnknownEntity,
WindowOpenDetectionMethod,
)
@@ -241,6 +242,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
]
),
vol.Optional(CONF_AC_MODE, default=False): cv.boolean,
vol.Optional(CONF_INVERSE_SWITCH, default=False): cv.boolean,
}
)

View File

@@ -90,6 +90,7 @@ CONF_AUTO_REGULATION_MEDIUM= "auto_regulation_medium"
CONF_AUTO_REGULATION_STRONG= "auto_regulation_strong"
CONF_AUTO_REGULATION_DTEMP="auto_regulation_dtemp"
CONF_AUTO_REGULATION_PERIOD_MIN="auto_regulation_periode_min"
CONF_INVERSE_SWITCH="inverse_switch_command"
CONF_PRESETS = {
p: f"{p}_temp"
@@ -193,7 +194,8 @@ ALL_CONF = (
CONF_VALVE_4,
CONF_AUTO_REGULATION_MODE,
CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN
CONF_AUTO_REGULATION_PERIOD_MIN,
CONF_INVERSE_SWITCH
]
+ CONF_PRESETS_VALUES
+ CONF_PRESETS_AWAY_VALUES

View File

@@ -26,6 +26,10 @@ class PITemperatureRegulator:
self.accumulated_error:float = 0
self.accumulated_error_threshold:float = accumulated_error_threshold
def reset_accumulated_error(self):
""" Reset the accumulated error """
self.accumulated_error = 0
def set_target_temp(self, target_temp):
""" Set the new target_temp"""
self.target_temp = target_temp

View File

@@ -41,7 +41,8 @@
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period"
"auto_regulation_periode_min": "Regulation minimal period",
"inverse_switch_command": "Inverse switch command"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -60,7 +61,8 @@
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_periode_min": "Duration in minutes between two regulation update"
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command"
}
},
"tpi": {
@@ -208,7 +210,8 @@
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period"
"auto_regulation_periode_min": "Regulation minimal period",
"inverse_switch_command": "Inverse switch command"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -227,7 +230,8 @@
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_periode_min": "Duration in minutes between two regulation update"
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command"
}
},
"tpi": {

View File

@@ -11,6 +11,7 @@ from .const import (
CONF_HEATER_2,
CONF_HEATER_3,
CONF_HEATER_4,
CONF_INVERSE_SWITCH,
overrides
)
@@ -34,12 +35,18 @@ class ThermostatOverSwitch(BaseThermostat):
# def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
# """Initialize the thermostat over switch."""
# super().__init__(hass, unique_id, name, entry_infos)
_is_inversed: bool = None
@property
def is_over_switch(self) -> bool:
""" True if the Thermostat is over_switch"""
return True
@property
def is_inversed(self) -> bool:
""" True if the switch is inversed (for pilot wire and diode)"""
return self._is_inversed is True
@overrides
def post_init(self, entry_infos):
""" Initialize the Thermostat"""
@@ -73,6 +80,7 @@ class ThermostatOverSwitch(BaseThermostat):
)
)
self._is_inversed = entry_infos.get(CONF_INVERSE_SWITCH) is True
self._should_relaunch_control_heating = False
@overrides

View File

@@ -41,7 +41,8 @@
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period"
"auto_regulation_periode_min": "Regulation minimal period",
"inverse_switch_command": "Inverse switch command"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -60,7 +61,8 @@
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_periode_min": "Duration in minutes between two regulation update"
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command"
}
},
"tpi": {
@@ -208,7 +210,8 @@
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period"
"auto_regulation_periode_min": "Regulation minimal period",
"inverse_switch_command": "Inverse switch command"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -227,7 +230,8 @@
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_periode_min": "Duration in minutes between two regulation update"
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command"
}
},
"tpi": {

View File

@@ -41,7 +41,8 @@
"valve_entity4_id": "4ème valve number",
"auto_regulation_mode": "Auto-régulation",
"auto_regulation_dtemp": "Seuil de régulation",
"auto_regulation_periode_min": "Période minimale de régulation"
"auto_regulation_periode_min": "Période minimale de régulation",
"inverse_switch_command": "Inverser la commande"
},
"data_description": {
"heater_entity_id": "Entity id du 1er radiateur obligatoire",
@@ -60,7 +61,8 @@
"valve_entity4_id": "Entity id de la 4ème valve",
"auto_regulation_mode": "Ajustement automatique de la température cible",
"auto_regulation_dtemp": "Le seuil en ° au-dessous duquel la régulation ne sera pas envoyée",
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation"
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode"
}
},
"tpi": {
@@ -209,7 +211,8 @@
"valve_entity4_id": "4ème valve",
"auto_regulation_mode": "Auto-regulation",
"auto_regulation_dtemp": "Seuil de régulation",
"auto_regulation_periode_min": "Période minimale de régulation"
"auto_regulation_periode_min": "Période minimale de régulation",
"inverse_switch_command": "Inverser la commande"
},
"data_description": {
"heater_entity_id": "Entity id du 1er radiateur obligatoire",
@@ -228,7 +231,8 @@
"valve_entity4_id": "Entity id de la 4ème valve",
"auto_regulation_mode": "Ajustement automatique de la consigne",
"auto_regulation_dtemp": "Le seuil en ° au-dessous duquel la régulation ne sera pas envoyée",
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation"
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode"
}
},
"tpi": {

View File

@@ -39,7 +39,8 @@
"valve_entity2_id": "Secondo valvola numero",
"valve_entity3_id": "Terzo valvola numero",
"valve_entity4_id": "Quarto valvola numero",
"auto_regulation_mode": "Autoregolamentazione"
"auto_regulation_mode": "Autoregolamentazione",
"inverse_switch_command": "Comando inverso"
},
"data_description": {
"heater_entity_id": "Entity id obbligatoria del primo riscaldatore",
@@ -56,7 +57,8 @@
"valve_entity2_id": "Entity id del secondo valvola numero",
"valve_entity3_id": "Entity id del terzo valvola numero",
"valve_entity4_id": "Entity id del quarto valvola numero",
"auto_regulation_mode": "Regolazione automatica della temperatura target"
"auto_regulation_mode": "Regolazione automatica della temperatura target",
"inverse_switch_command": "Inverte il controllo dell'interruttore per un'installazione con filo pilota e diodo"
}
},
"tpi": {
@@ -195,7 +197,8 @@
"valve_entity2_id": "Secondo valvola numero",
"valve_entity3_id": "Terzo valvola numero",
"valve_entity4_id": "Quarto valvola numero",
"auto_regulation_mode": "Autoregolamentazione"
"auto_regulation_mode": "Autoregolamentazione",
"inverse_switch_command": "Comando inverso"
},
"data_description": {
"heater_entity_id": "Entity id obbligatoria del primo riscaldatore",
@@ -212,7 +215,8 @@
"valve_entity2_id": "Entity id del secondo valvola numero",
"valve_entity3_id": "Entity id del terzo valvola numero",
"valve_entity4_id": "Entity id del quarto valvola numero",
"auto_regulation_mode": "Autoregolamentazione"
"auto_regulation_mode": "Autoregolamentazione",
"inverse_switch_command": "Inverte il controllo dell'interruttore per un'installazione con filo pilota e diodo"
}
},
"tpi": {

View File

@@ -38,7 +38,11 @@
"valve_entity_id": "1. ventil číslo",
"valve_entity2_id": "2. ventil číslo",
"valve_entity3_id": "3. ventil číslo",
"valve_entity4_id": "4. ventil číslo"
"valve_entity4_id": "4. ventil číslo",
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period",
"inverse_switch_command": "Inverse switch command"
},
"data_description": {
"heater_entity_id": "ID entity povinného ohrievača",
@@ -54,7 +58,11 @@
"valve_entity_id": "1. ventil číslo entity id",
"valve_entity2_id": "2. ventil číslo entity id",
"valve_entity3_id": "3. ventil číslo entity id",
"valve_entity4_id": "4. ventil číslo entity id"
"valve_entity4_id": "4. ventil číslo entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command"
}
},
"tpi": {
@@ -199,7 +207,11 @@
"valve_entity_id": "1. ventil číslo",
"valve_entity2_id": "2. ventil číslo",
"valve_entity3_id": "3. ventil číslo",
"valve_entity4_id": "4. ventil číslo"
"valve_entity4_id": "4. ventil číslo",
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period",
"inverse_switch_command": "Inverse switch command"
},
"data_description": {
"heater_entity_id": "ID entity povinného ohrievača",
@@ -215,7 +227,11 @@
"valve_entity_id": "1. ventil číslo entity id",
"valve_entity2_id": "2. ventil číslo entity id",
"valve_entity3_id": "3. ventil číslo entity id",
"valve_entity4_id": "4. ventil číslo entity id"
"valve_entity4_id": "4. ventil číslo entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command"
}
},
"tpi": {
@@ -329,6 +345,14 @@
"thermostat_over_climate": "Termostat nad iným termostatom",
"thermostat_over_valve": "Thermostat over a valve"
}
},
"auto_regulation_mode": {
"options": {
"auto_regulation_strong": "Strong",
"auto_regulation_medium": "Medium",
"auto_regulation_light": "Light",
"auto_regulation_none": "No auto-regulation"
}
}
},
"entity": {

View File

@@ -9,7 +9,7 @@ from homeassistant.const import ATTR_ENTITY_ID, STATE_ON, UnitOfTemperature
from homeassistant.exceptions import ServiceNotFound
from homeassistant.core import HomeAssistant, DOMAIN as HA_DOMAIN, CALLBACK_TYPE
from homeassistant.core import HomeAssistant, CALLBACK_TYPE
from homeassistant.components.climate import (
ClimateEntity,
ClimateEntityFeature,
@@ -104,37 +104,27 @@ class UnderlyingEntity:
"""If the toggleable device is currently active."""
return None
async def turn_off(self):
"""Turn heater toggleable device off."""
_LOGGER.debug("%s - Stopping underlying entity %s", self, self._entity_id)
# This may fails if called after shutdown
try:
data = {ATTR_ENTITY_ID: self._entity_id}
await self._hass.services.async_call(
HA_DOMAIN,
SERVICE_TURN_OFF,
data,
)
except ServiceNotFound as err:
_LOGGER.error(err)
async def turn_on(self):
"""Turn heater toggleable device on."""
_LOGGER.debug("%s - Starting underlying entity %s", self, self._entity_id)
try:
data = {ATTR_ENTITY_ID: self._entity_id}
await self._hass.services.async_call(
HA_DOMAIN,
SERVICE_TURN_ON,
data,
)
except ServiceNotFound as err:
_LOGGER.error(err)
async def set_temperature(self, temperature, max_temp, min_temp):
"""Set the target temperature"""
return
# This should be the correct way to handle turn_off and turn_on but this breaks the unit test
# will an not understandable error: TypeError: object MagicMock can't be used in 'await' expression
async def turn_off(self):
""" Turn off the underlying equipement.
Need to be overriden"""
return NotImplementedError
async def turn_on(self):
""" Turn off the underlying equipement.
Need to be overriden"""
return NotImplementedError
@property
def is_inversed(self):
""" Tells if the switch command should be inversed"""
return False
def remove_entity(self):
"""Remove the underlying entity"""
return
@@ -212,6 +202,13 @@ class UnderlyingSwitch(UnderlyingEntity):
"""The initial delay for this class"""
return self._initial_delay_sec
@overrides
@property
def is_inversed(self):
""" Tells if the switch command should be inversed"""
return self._thermostat.is_inversed
# @overrides this breaks some unit tests TypeError: object MagicMock can't be used in 'await' expression
async def set_hvac_mode(self, hvac_mode: HVACMode) -> bool:
"""Set the HVACmode. Returns true if something have change"""
@@ -229,7 +226,41 @@ class UnderlyingSwitch(UnderlyingEntity):
@property
def is_device_active(self):
"""If the toggleable device is currently active."""
return self._hass.states.is_state(self._entity_id, STATE_ON)
real_state = self._hass.states.is_state(self._entity_id, STATE_ON)
return self.is_inversed and not real_state
# @overrides this breaks some unit tests TypeError: object MagicMock can't be used in 'await' expression
async def turn_off(self):
"""Turn heater toggleable device off."""
_LOGGER.debug("%s - Stopping underlying entity %s", self, self._entity_id)
command = SERVICE_TURN_OFF if not self.is_inversed else SERVICE_TURN_ON
domain = self._entity_id.split('.')[0]
# This may fails if called after shutdown
try:
data = {ATTR_ENTITY_ID: self._entity_id}
await self._hass.services.async_call(
domain,
command,
data,
)
except ServiceNotFound as err:
_LOGGER.error(err)
async def turn_on(self):
"""Turn heater toggleable device on."""
_LOGGER.debug("%s - Starting underlying entity %s", self, self._entity_id)
command = SERVICE_TURN_ON if not self.is_inversed else SERVICE_TURN_OFF
domain = self._entity_id.split('.')[0]
try:
data = {ATTR_ENTITY_ID: self._entity_id}
await self._hass.services.async_call(
domain,
command,
data,
)
except ServiceNotFound as err:
_LOGGER.error(err)
@overrides
async def start_cycle(
@@ -380,6 +411,7 @@ class UnderlyingSwitch(UnderlyingEntity):
# increment energy at the end of the cycle
self._thermostat.incremente_energy()
@overrides
def remove_entity(self):
"""Remove the entity after stopping its cycle"""
self._cancel_cycle()

View File

@@ -54,7 +54,8 @@ from custom_components.versatile_thermostat.const import (
CONF_AUTO_REGULATION_STRONG,
CONF_AUTO_REGULATION_NONE,
CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN
CONF_AUTO_REGULATION_PERIOD_MIN,
CONF_INVERSE_SWITCH
)
MOCK_TH_OVER_SWITCH_USER_CONFIG = {
CONF_NAME: "TheOverSwitchMockName",
@@ -101,13 +102,15 @@ MOCK_TH_OVER_CLIMATE_USER_CONFIG = {
MOCK_TH_OVER_SWITCH_TYPE_CONFIG = {
CONF_HEATER: "switch.mock_switch",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_AC_MODE: False
CONF_AC_MODE: False,
CONF_INVERSE_SWITCH: False
}
MOCK_TH_OVER_SWITCH_AC_TYPE_CONFIG = {
CONF_HEATER: "switch.mock_air_conditioner",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_AC_MODE: True,
CONF_INVERSE_SWITCH: False
}
MOCK_TH_OVER_4SWITCH_TYPE_CONFIG = {
@@ -117,6 +120,7 @@ MOCK_TH_OVER_4SWITCH_TYPE_CONFIG = {
CONF_HEATER_4: "switch.mock_4switch3",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_AC_MODE: False,
CONF_INVERSE_SWITCH: False
}
MOCK_TH_OVER_SWITCH_TPI_CONFIG = {

View File

@@ -17,7 +17,7 @@ async def test_show_form(hass: HomeAssistant) -> None:
# Init the API
# hass.data["custom_components"] = None
# loader.async_get_custom_components(hass)
# VersatileThermostatAPI(hass)
# BaseThermostatAPI(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
@@ -369,7 +369,7 @@ async def test_user_config_flow_over_4_switches(
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False
}
TYPE_CONFIG = { # pylint: disable=wildcard-import, invalid-name
@@ -434,6 +434,7 @@ async def test_user_config_flow_over_4_switches(
| MOCK_TH_OVER_SWITCH_TPI_CONFIG
| MOCK_PRESETS_CONFIG
| MOCK_ADVANCED_CONFIG
| { CONF_INVERSE_SWITCH: False }
)
assert result["result"]
assert result["result"].domain == DOMAIN

View File

@@ -0,0 +1,124 @@
# pylint: disable=unused-argument, line-too-long, protected-access
""" Test the Window management """
import asyncio
import logging
from unittest.mock import patch, call
from datetime import datetime, timedelta
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_inverted_switch(hass: HomeAssistant, skip_hass_states_is_state):
"""Test the Window auto management"""
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverSwitchMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOverSwitchMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 21,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_HEATER: "switch.mock_switch",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 0.1,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 0.1,
CONF_WINDOW_AUTO_MAX_DURATION: 0, # Should be 0 for test
CONF_INVERSE_SWITCH: True
},
)
with patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"homeassistant.core.StateMachine.is_state", return_value=True # switch is On
):
entity: ThermostatOverSwitch = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
assert entity.is_inversed
tz = get_tz(hass) # pylint: disable=invalid-name
now = datetime.now(tz)
tpi_algo = entity._prop_algorithm
assert tpi_algo
await entity.async_set_hvac_mode(HVACMode.HEAT)
await entity.async_set_preset_mode(PRESET_BOOST)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST
assert entity.target_temperature == 21
assert entity.is_device_active is False
assert mock_service_call.call_count == 0
# 1. Make the temperature down to activate the switch
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
), patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"homeassistant.core.StateMachine.is_state", return_value=True # switch is Off
):
event_timestamp = now - timedelta(minutes=4)
await send_temperature_change_event(entity, 19, event_timestamp)
# The heater turns on
assert entity.hvac_mode is HVACMode.HEAT
# not updated cause mocked assert entity.is_device_active is True
assert mock_service_call.call_count == 1
mock_service_call.assert_has_calls([
call.async_call('switch', SERVICE_TURN_OFF, {'entity_id': 'switch.mock_switch'}),
])
# 2. Make the temperature up to deactivate the switch
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
), patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"homeassistant.core.StateMachine.is_state", return_value=False # switch is On -> it should turned off
):
event_timestamp = now - timedelta(minutes=3)
await send_temperature_change_event(entity, 25, event_timestamp)
# The heater turns on
assert entity.hvac_mode is HVACMode.HEAT
# not updated cause mocked assert entity.is_device_active is False
# there is no change because the cycle is currenlty running.
# we should simulate the end of the cycle to see oif underlying switch turns on
await entity._underlyings[0].turn_off()
assert mock_service_call.call_count == 1
mock_service_call.assert_has_calls([
call.async_call('switch', SERVICE_TURN_ON, {'entity_id': 'switch.mock_switch'}),
])
# Clean the entity
entity.remove_thermostat()

View File

@@ -1,10 +1,12 @@
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long, unused-variable
""" Test the Window management """
import asyncio
from datetime import datetime, timedelta
import logging
from unittest.mock import patch, call, PropertyMock
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
from unittest.mock import patch
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
logging.getLogger().setLevel(logging.DEBUG)
@@ -54,7 +56,7 @@ async def test_movement_management_time_not_enough(
},
)
entity: VersatileThermostat = await create_thermostat(
entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
@@ -251,7 +253,7 @@ async def test_movement_management_time_enough_and_presence(
},
)
entity: VersatileThermostat = await create_thermostat(
entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
@@ -383,7 +385,7 @@ async def test_movement_management_time_enoughand_not_presence(
},
)
entity: VersatileThermostat = await create_thermostat(
entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
@@ -517,7 +519,7 @@ async def test_movement_management_with_stop_during_condition(
},
)
entity: VersatileThermostat = await create_thermostat(
entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
@@ -597,4 +599,3 @@ async def test_movement_management_with_stop_during_condition(
assert entity.target_temperature == 19 # Boost
assert entity.motion_state is "on" # switch to movement on
assert entity.presence_state is "off" # Non change

View File

@@ -1,9 +1,12 @@
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long, unused-variable
""" Test the Multiple switch management """
import asyncio
from unittest.mock import patch, call, ANY
from datetime import datetime, timedelta
import logging
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
logging.getLogger().setLevel(logging.DEBUG)
@@ -50,7 +53,7 @@ async def test_one_switch_cycle(
},
)
entity: VersatileThermostat = await create_thermostat(
entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theover4switchmockname"
)
assert entity
@@ -260,7 +263,7 @@ async def test_multiple_switchs(
},
)
entity: VersatileThermostat = await create_thermostat(
entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theover4switchmockname"
)
assert entity
@@ -396,7 +399,7 @@ async def test_multiple_climates(
},
)
entity: VersatileThermostat = await create_thermostat(
entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theover4climatemockname"
)
assert entity
@@ -496,7 +499,7 @@ async def test_multiple_climates_underlying_changes(
},
)
entity: VersatileThermostat = await create_thermostat(
entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theover4climatemockname"
)
assert entity

View File

@@ -16,6 +16,7 @@ def test_pi_algorithm_basics():
# to reset the accumulated erro
the_algo.set_target_temp(20)
the_algo.reset_accumulated_error()
# Test the accumulator threshold effect and offset_max
assert the_algo.calculate_regulated_temperature(10, 10) == 22 # +2
@@ -23,8 +24,8 @@ def test_pi_algorithm_basics():
assert the_algo.calculate_regulated_temperature(10, 10) == 22
# Will keep infinitly 22.0
# to reset the accumulated erro
the_algo.set_target_temp(20)
# to reset the accumulated error
the_algo.reset_accumulated_error()
assert the_algo.calculate_regulated_temperature(18, 10) == 21.5 # +1.5
assert the_algo.calculate_regulated_temperature(18.1, 10) == 21.6 # +1.6
assert the_algo.calculate_regulated_temperature(18.3, 10) == 21.6 # +1.6
@@ -104,6 +105,7 @@ def test_pi_algorithm_medium():
# to reset the accumulated erro
the_algo.set_target_temp(20)
the_algo.reset_accumulated_error()
# Test the error acculation effect
assert the_algo.calculate_regulated_temperature(19, 5) == 22.1
assert the_algo.calculate_regulated_temperature(19, 5) == 22.2
@@ -157,6 +159,7 @@ def test_pi_algorithm_strong():
# to reset the accumulated erro
the_algo.set_target_temp(20)
the_algo.reset_accumulated_error()
# Test the error acculation effect
assert the_algo.calculate_regulated_temperature(19, 10) == 22.8
assert the_algo.calculate_regulated_temperature(19, 10) == 23