Compare commits

...

5 Commits

Author SHA1 Message Date
Jean-Marc Collin 047c847f3c Fix rounding regulated + offset (#384)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-02-16 08:46:11 +01:00
Paulo Ferreira de Castro 91e39f885f Improvements to the development environment (#383)
* Update Home Assistant dev version in requirements_dev.txt

* Avoid "Error starting FFmpeg" error in VSCode dev container logs

* Add "editor.formatOnSaveMode": "modifications" to .vscode/settings.json
2024-02-16 07:30:37 +01:00
Paulo Ferreira de Castro dce8fa2ed6 Make the switch keep-alive callback conditional on the entity state (#382) 2024-02-16 07:23:30 +01:00
Paulo Ferreira de Castro a440b35815 Prevent disabled heating warning loop while HVACMode is OFF (#374) 2024-02-04 20:58:22 +01:00
Jean-Marc Collin e52666b9d9 Add logs in troubleshooting 2024-02-04 09:28:37 +00:00
17 changed files with 114 additions and 64 deletions
+2
View File
@@ -0,0 +1,2 @@
FROM mcr.microsoft.com/devcontainers/python:1-3.11
RUN apt update && apt install -y ffmpeg
-3
View File
@@ -1,8 +1,5 @@
default_config: default_config:
# ffmeg
ffmpeg:
logger: logger:
default: info default: info
logs: logs:
+3 -1
View File
@@ -1,7 +1,9 @@
// See https://aka.ms/vscode-remote/devcontainer.json for format details. // See https://aka.ms/vscode-remote/devcontainer.json for format details.
// "image": "ghcr.io/ludeeus/devcontainer/integration:latest", // "image": "ghcr.io/ludeeus/devcontainer/integration:latest",
{ {
"image": "mcr.microsoft.com/devcontainers/python:1-3.11-bullseye", "build": {
"dockerfile": "Dockerfile"
},
"name": "Versatile Thermostat integration", "name": "Versatile Thermostat integration",
"appPort": [ "appPort": [
"8123:8123" "8123:8123"
+2 -1
View File
@@ -1,7 +1,8 @@
{ {
"[python]": { "[python]": {
"editor.defaultFormatter": "ms-python.black-formatter", "editor.defaultFormatter": "ms-python.black-formatter",
"editor.formatOnSave": true "editor.formatOnSave": true,
"editor.formatOnSaveMode": "modifications"
}, },
"pylint.lintOnChange": false, "pylint.lintOnChange": false,
"files.associations": { "files.associations": {
+10
View File
@@ -81,6 +81,7 @@
- [Comment être averti lorsque cela se produit ?](#comment-être-averti-lorsque-cela-se-produit-) - [Comment être averti lorsque cela se produit ?](#comment-être-averti-lorsque-cela-se-produit-)
- [Comment réparer ?](#comment-réparer-) - [Comment réparer ?](#comment-réparer-)
- [Utilisation d'un groupe de personnes comme capteur de présence](#utilisation-dun-groupe-de-personnes-comme-capteur-de-présence) - [Utilisation d'un groupe de personnes comme capteur de présence](#utilisation-dun-groupe-de-personnes-comme-capteur-de-présence)
- [Activer les logs du Versatile Thermostat](#activer-les-logs-du-versatile-thermostat)
Ce composant personnalisé pour Home Assistant est une mise à niveau et est une réécriture complète du composant "Awesome thermostat" (voir [Github](https://github.com/dadge/awesome_thermostat)) avec l'ajout de fonctionnalités. Ce composant personnalisé pour Home Assistant est une mise à niveau et est une réécriture complète du composant "Awesome thermostat" (voir [Github](https://github.com/dadge/awesome_thermostat)) avec l'ajout de fonctionnalités.
@@ -1466,6 +1467,15 @@ template: !include templates.yaml
... ...
``` ```
## Activer les logs du Versatile Thermostat
Des fois, vous aurez besoin d'activer les logs pour afiner les analyses. Pour cela, éditer le fichier `logger.yaml` de votre configuration et configurer les logs comme suit :
```
default: xxxx
logs:
custom_components.versatile_thermostat: info
```
Vous devez recharger la configuration yaml (Outils de dev / Yaml / Toute la configuration Yaml) ou redémarrer Home Assistant pour que ce changement soit pris en compte.
*** ***
[versatile_thermostat]: https://github.com/jmcollin78/versatile_thermostat [versatile_thermostat]: https://github.com/jmcollin78/versatile_thermostat
+10
View File
@@ -81,6 +81,7 @@
- [How can I be notified when this happens?](#how-can-i-be-notified-when-this-happens) - [How can I be notified when this happens?](#how-can-i-be-notified-when-this-happens)
- [How to repair?](#how-to-repair) - [How to repair?](#how-to-repair)
- [Using a group of people as a presence sensor](#using-a-group-of-people-as-a-presence-sensor) - [Using a group of people as a presence sensor](#using-a-group-of-people-as-a-presence-sensor)
- [Enable Versatile Thermostat logs](#enable-versatile-thermostat-logs)
This custom component for Home Assistant is an upgrade and is a complete rewrite of the component "Awesome thermostat" (see [Github](https://github.com/dadge/awesome_thermostat)) with addition of features. This custom component for Home Assistant is an upgrade and is a complete rewrite of the component "Awesome thermostat" (see [Github](https://github.com/dadge/awesome_thermostat)) with addition of features.
@@ -1449,6 +1450,15 @@ template: !include templates.yaml
... ...
``` ```
## Enable Versatile Thermostat logs
Sometimes you will need to enable logs to refine the analyses. To do this, edit the `logger.yaml` file of your configuration and configure the logs as follows:
```
default: xxxx
logs:
custom_components.versatile_thermostat: info
```
You must reload the yaml configuration (Dev Tools / Yaml / All Yaml configuration) or restart Home Assistant for this change to take effect.
*** ***
[versatile_thermostat]: https://github.com/jmcollin78/versatile_thermostat [versatile_thermostat]: https://github.com/jmcollin78/versatile_thermostat
@@ -799,7 +799,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
self._target_temp, self._target_temp,
self._cur_temp, self._cur_temp,
self._cur_ext_temp, self._cur_ext_temp,
self._hvac_mode == HVACMode.COOL, self._hvac_mode or HVACMode.OFF,
) )
self.hass.create_task(self._check_initial_state()) self.hass.create_task(self._check_initial_state())
@@ -1,6 +1,8 @@
""" The TPI calculation module """ """ The TPI calculation module """
import logging import logging
from homeassistant.components.climate import HVACMode
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
PROPORTIONAL_FUNCTION_ATAN = "atan" PROPORTIONAL_FUNCTION_ATAN = "atan"
@@ -46,19 +48,20 @@ class PropAlgorithm:
def calculate( def calculate(
self, self,
target_temp: float, target_temp: float | None,
current_temp: float, current_temp: float | None,
ext_current_temp: float, ext_current_temp: float | None,
cooling=False, hvac_mode: HVACMode,
): ):
"""Do the calculation of the duration""" """Do the calculation of the duration"""
if target_temp is None or current_temp is None: if target_temp is None or current_temp is None:
_LOGGER.warning( log = _LOGGER.debug if hvac_mode == HVACMode.OFF else _LOGGER.warning
log(
"Proportional algorithm: calculation is not possible cause target_temp or current_temp is null. Heating/cooling will be disabled" # pylint: disable=line-too-long "Proportional algorithm: calculation is not possible cause target_temp or current_temp is null. Heating/cooling will be disabled" # pylint: disable=line-too-long
) )
self._calculated_on_percent = 0 self._calculated_on_percent = 0
else: else:
if cooling: if hvac_mode == HVACMode.COOL:
delta_temp = current_temp - target_temp delta_temp = current_temp - target_temp
delta_ext_temp = ( delta_ext_temp = (
ext_current_temp ext_current_temp
@@ -216,7 +216,7 @@ class ThermostatOverClimate(BaseThermostat):
): ):
offset_temp = device_temp - self.current_temperature offset_temp = device_temp - self.current_temperature
target_temp = self.regulated_target_temp + offset_temp target_temp = round_to_nearest(self.regulated_target_temp + offset_temp, self._auto_regulation_dtemp)
_LOGGER.debug( _LOGGER.debug(
"%s - The device offset temp for regulation is %.2f - internal temp is %.2f. New target is %.2f", "%s - The device offset temp for regulation is %.2f - internal temp is %.2f. New target is %.2f",
@@ -491,9 +491,9 @@ class ThermostatOverClimate(BaseThermostat):
super().update_custom_attributes() super().update_custom_attributes()
self._attr_extra_state_attributes["is_over_climate"] = self.is_over_climate self._attr_extra_state_attributes["is_over_climate"] = self.is_over_climate
self._attr_extra_state_attributes[ self._attr_extra_state_attributes["start_hvac_action_date"] = (
"start_hvac_action_date" self._underlying_climate_start_hvac_action_date
] = self._underlying_climate_start_hvac_action_date )
self._attr_extra_state_attributes["underlying_climate_0"] = self._underlyings[ self._attr_extra_state_attributes["underlying_climate_0"] = self._underlyings[
0 0
].entity_id ].entity_id
@@ -509,32 +509,32 @@ class ThermostatOverClimate(BaseThermostat):
if self.is_regulated: if self.is_regulated:
self._attr_extra_state_attributes["is_regulated"] = self.is_regulated self._attr_extra_state_attributes["is_regulated"] = self.is_regulated
self._attr_extra_state_attributes[ self._attr_extra_state_attributes["regulated_target_temperature"] = (
"regulated_target_temperature" self._regulated_target_temp
] = self._regulated_target_temp )
self._attr_extra_state_attributes[ self._attr_extra_state_attributes["auto_regulation_mode"] = (
"auto_regulation_mode" self.auto_regulation_mode
] = self.auto_regulation_mode )
self._attr_extra_state_attributes[ self._attr_extra_state_attributes["regulation_accumulated_error"] = (
"regulation_accumulated_error" self._regulation_algo.accumulated_error
] = self._regulation_algo.accumulated_error )
self._attr_extra_state_attributes["auto_fan_mode"] = self.auto_fan_mode self._attr_extra_state_attributes["auto_fan_mode"] = self.auto_fan_mode
self._attr_extra_state_attributes[ self._attr_extra_state_attributes["current_auto_fan_mode"] = (
"current_auto_fan_mode" self._current_auto_fan_mode
] = self._current_auto_fan_mode )
self._attr_extra_state_attributes[ self._attr_extra_state_attributes["auto_activated_fan_mode"] = (
"auto_activated_fan_mode" self._auto_activated_fan_mode
] = self._auto_activated_fan_mode )
self._attr_extra_state_attributes[ self._attr_extra_state_attributes["auto_deactivated_fan_mode"] = (
"auto_deactivated_fan_mode" self._auto_deactivated_fan_mode
] = self._auto_deactivated_fan_mode )
self._attr_extra_state_attributes[ self._attr_extra_state_attributes["auto_regulation_use_device_temp"] = (
"auto_regulation_use_device_temp" self.auto_regulation_use_device_temp
] = self.auto_regulation_use_device_temp )
self.async_write_ha_state() self.async_write_ha_state()
_LOGGER.debug( _LOGGER.debug(
@@ -183,7 +183,7 @@ class ThermostatOverSwitch(BaseThermostat):
self._target_temp, self._target_temp,
self._cur_temp, self._cur_temp,
self._cur_ext_temp, self._cur_ext_temp,
self._hvac_mode == HVACMode.COOL, self._hvac_mode or HVACMode.OFF,
) )
self.update_custom_attributes() self.update_custom_attributes()
self.async_write_ha_state() self.async_write_ha_state()
@@ -234,7 +234,7 @@ class ThermostatOverValve(BaseThermostat):
self._target_temp, self._target_temp,
self._cur_temp, self._cur_temp,
self._cur_ext_temp, self._cur_ext_temp,
self._hvac_mode == HVACMode.COOL, self._hvac_mode or HVACMode.OFF,
) )
new_valve_percent = round( new_valve_percent = round(
@@ -220,9 +220,7 @@ class UnderlyingSwitch(UnderlyingEntity):
@overrides @overrides
def startup(self): def startup(self):
super().startup() super().startup()
self._keep_alive.set_async_action( self._keep_alive.set_async_action(self._keep_alive_callback)
self.turn_on if self.is_device_active else self.turn_off
)
# @overrides this breaks some unit tests TypeError: object MagicMock can't be used in 'await' expression # @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: async def set_hvac_mode(self, hvac_mode: HVACMode) -> bool:
@@ -247,9 +245,14 @@ class UnderlyingSwitch(UnderlyingEntity):
not self.is_inversed and real_state not self.is_inversed and real_state
) )
async def _keep_alive_callback(self):
"""Keep alive: Turn on if already turned on, turn off if already turned off."""
await (self.turn_on() if self.is_device_active else self.turn_off())
# @overrides this breaks some unit tests TypeError: object MagicMock can't be used in 'await' expression # @overrides this breaks some unit tests TypeError: object MagicMock can't be used in 'await' expression
async def turn_off(self): async def turn_off(self):
"""Turn heater toggleable device off.""" """Turn heater toggleable device off."""
self._keep_alive.cancel() # Cancel early to avoid a turn_on/turn_off race condition
_LOGGER.debug("%s - Stopping underlying entity %s", self, self._entity_id) _LOGGER.debug("%s - Stopping underlying entity %s", self, self._entity_id)
command = SERVICE_TURN_OFF if not self.is_inversed else SERVICE_TURN_ON command = SERVICE_TURN_OFF if not self.is_inversed else SERVICE_TURN_ON
domain = self._entity_id.split(".")[0] domain = self._entity_id.split(".")[0]
@@ -258,7 +261,7 @@ class UnderlyingSwitch(UnderlyingEntity):
try: try:
data = {ATTR_ENTITY_ID: self._entity_id} data = {ATTR_ENTITY_ID: self._entity_id}
await self._hass.services.async_call(domain, command, data) await self._hass.services.async_call(domain, command, data)
self._keep_alive.set_async_action(self.turn_off) self._keep_alive.set_async_action(self._keep_alive_callback)
except Exception: except Exception:
self._keep_alive.cancel() self._keep_alive.cancel()
raise raise
@@ -267,6 +270,7 @@ class UnderlyingSwitch(UnderlyingEntity):
async def turn_on(self): async def turn_on(self):
"""Turn heater toggleable device on.""" """Turn heater toggleable device on."""
self._keep_alive.cancel() # Cancel early to avoid a turn_on/turn_off race condition
_LOGGER.debug("%s - Starting underlying entity %s", self, self._entity_id) _LOGGER.debug("%s - Starting underlying entity %s", self, self._entity_id)
command = SERVICE_TURN_ON if not self.is_inversed else SERVICE_TURN_OFF command = SERVICE_TURN_ON if not self.is_inversed else SERVICE_TURN_OFF
domain = self._entity_id.split(".")[0] domain = self._entity_id.split(".")[0]
@@ -274,7 +278,7 @@ class UnderlyingSwitch(UnderlyingEntity):
try: try:
data = {ATTR_ENTITY_ID: self._entity_id} data = {ATTR_ENTITY_ID: self._entity_id}
await self._hass.services.async_call(domain, command, data) await self._hass.services.async_call(domain, command, data)
self._keep_alive.set_async_action(self.turn_on) self._keep_alive.set_async_action(self._keep_alive_callback)
except Exception: except Exception:
self._keep_alive.cancel() self._keep_alive.cancel()
raise raise
+1 -2
View File
@@ -1,2 +1 @@
homeassistant==2023.12.1 homeassistant==2024.2.1
ffmpeg
+2 -1
View File
@@ -1,4 +1,5 @@
""" The commons const for all tests """ """ The commons const for all tests """
from homeassistant.components.climate.const import ( # pylint: disable=unused-import from homeassistant.components.climate.const import ( # pylint: disable=unused-import
PRESET_BOOST, PRESET_BOOST,
PRESET_COMFORT, PRESET_COMFORT,
@@ -52,7 +53,7 @@ MOCK_TH_OVER_CLIMATE_MAIN_CONFIG = {
CONF_CYCLE_MIN: 5, CONF_CYCLE_MIN: 5,
CONF_DEVICE_POWER: 1, CONF_DEVICE_POWER: 1,
CONF_USE_MAIN_CENTRAL_CONFIG: False, CONF_USE_MAIN_CENTRAL_CONFIG: False,
CONF_USE_CENTRAL_MODE: True CONF_USE_CENTRAL_MODE: True,
# Keep default values which are False # Keep default values which are False
} }
+8 -8
View File
@@ -387,7 +387,7 @@ async def test_over_climate_regulation_use_device_temp(
title="TheOverClimateMockName", title="TheOverClimateMockName",
unique_id="uniqueId", unique_id="uniqueId",
# This is include a medium regulation # This is include a medium regulation
data=PARTIAL_CLIMATE_CONFIG_USE_DEVICE_TEMP, data=PARTIAL_CLIMATE_CONFIG_USE_DEVICE_TEMP | {CONF_AUTO_REGULATION_DTEMP: 0.5},
) )
tz = get_tz(hass) # pylint: disable=invalid-name tz = get_tz(hass) # pylint: disable=invalid-name
@@ -475,7 +475,7 @@ async def test_over_climate_regulation_use_device_temp(
# room temp is 15 # room temp is 15
# target is 18 # target is 18
# internal heater temp is 20 # internal heater temp is 20
fake_underlying_climate.set_current_temperature(20) fake_underlying_climate.set_current_temperature(20.1)
await entity.async_set_temperature(temperature=18) await entity.async_set_temperature(temperature=18)
await send_ext_temperature_change_event(entity, 9, event_timestamp) await send_ext_temperature_change_event(entity, 9, event_timestamp)
@@ -488,7 +488,7 @@ async def test_over_climate_regulation_use_device_temp(
# the regulated temperature should be under (device offset is -2) # the regulated temperature should be under (device offset is -2)
assert entity.regulated_target_temp > entity.target_temperature assert entity.regulated_target_temp > entity.target_temperature
assert entity.regulated_target_temp == 19.4 # 18 + 1.4 assert entity.regulated_target_temp == 19.5 # round(18 + 1.4, 0.5)
mock_service_call.assert_has_calls( mock_service_call.assert_has_calls(
[ [
@@ -497,7 +497,7 @@ async def test_over_climate_regulation_use_device_temp(
"set_temperature", "set_temperature",
{ {
"entity_id": "climate.mock_climate", "entity_id": "climate.mock_climate",
"temperature": 24.4, # 19.4 + 5 "temperature": 24.5, # round(19.5 + 5, 0.5)
"target_temp_high": 30, "target_temp_high": 30,
"target_temp_low": 15, "target_temp_low": 15,
}, },
@@ -511,7 +511,7 @@ async def test_over_climate_regulation_use_device_temp(
# internal heater temp is 27 # internal heater temp is 27
await entity.async_set_hvac_mode(HVACMode.COOL) await entity.async_set_hvac_mode(HVACMode.COOL)
await entity.async_set_temperature(temperature=23) await entity.async_set_temperature(temperature=23)
fake_underlying_climate.set_current_temperature(27) fake_underlying_climate.set_current_temperature(26.9)
await send_ext_temperature_change_event(entity, 30, event_timestamp) await send_ext_temperature_change_event(entity, 30, event_timestamp)
event_timestamp = now - timedelta(minutes=3) event_timestamp = now - timedelta(minutes=3)
@@ -521,9 +521,9 @@ async def test_over_climate_regulation_use_device_temp(
), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call: ), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
await send_temperature_change_event(entity, 25, event_timestamp) await send_temperature_change_event(entity, 25, event_timestamp)
# the regulated temperature should be upper (device offset is +2) # the regulated temperature should be upper (device offset is +1.9)
assert entity.regulated_target_temp < entity.target_temperature assert entity.regulated_target_temp < entity.target_temperature
assert entity.regulated_target_temp == 22.4 assert entity.regulated_target_temp == 22.5
mock_service_call.assert_has_calls( mock_service_call.assert_has_calls(
[ [
@@ -532,7 +532,7 @@ async def test_over_climate_regulation_use_device_temp(
"set_temperature", "set_temperature",
{ {
"entity_id": "climate.mock_climate", "entity_id": "climate.mock_climate",
"temperature": 24.4, # 22.4 + 2° of offset "temperature": 24.5, # round(22.5 + 1.9° of offset)
"target_temp_high": 30, "target_temp_high": 30,
"target_temp_low": 15, "target_temp_low": 15,
}, },
+2
View File
@@ -210,6 +210,7 @@ class TestKeepAlive:
common_mocks, common_mocks,
[call("switch", SERVICE_TURN_ON, {"entity_id": "switch.mock_switch"})], [call("switch", SERVICE_TURN_ON, {"entity_id": "switch.mock_switch"})],
) )
common_mocks.mock_is_state.return_value = True
# Call the keep-alive callback a few times (as if `async_track_time_interval` # Call the keep-alive callback a few times (as if `async_track_time_interval`
# had done it) and assert that the callback function is replaced each time. # had done it) and assert that the callback function is replaced each time.
@@ -240,6 +241,7 @@ class TestKeepAlive:
common_mocks, common_mocks,
[call("switch", SERVICE_TURN_OFF, {"entity_id": "switch.mock_switch"})], [call("switch", SERVICE_TURN_OFF, {"entity_id": "switch.mock_switch"})],
) )
common_mocks.mock_is_state.return_value = False
# Call the keep-alive callback a few times (as if `async_track_time_interval` # Call the keep-alive callback a few times (as if `async_track_time_interval`
# had done it) and assert that the callback function is replaced each time. # had done it) and assert that the callback function is replaced each time.
+28 -9
View File
@@ -1,6 +1,9 @@
""" Test the TPI algorithm """ """ Test the TPI algorithm """
from homeassistant.components.climate import HVACMode
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
from custom_components.versatile_thermostat.prop_algorithm import PropAlgorithm
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
@@ -42,53 +45,54 @@ async def test_tpi_calculation(
hass, entry, "climate.theoverswitchmockname" hass, entry, "climate.theoverswitchmockname"
) )
assert entity assert entity
assert entity._prop_algorithm # pylint: disable=protected-access
tpi_algo = entity._prop_algorithm # pylint: disable=protected-access tpi_algo: PropAlgorithm = entity._prop_algorithm # pylint: disable=protected-access
assert tpi_algo assert tpi_algo
tpi_algo.calculate(15, 10, 7) tpi_algo.calculate(15, 10, 7, HVACMode.HEAT)
assert tpi_algo.on_percent == 1 assert tpi_algo.on_percent == 1
assert tpi_algo.calculated_on_percent == 1 assert tpi_algo.calculated_on_percent == 1
assert tpi_algo.on_time_sec == 300 assert tpi_algo.on_time_sec == 300
assert tpi_algo.off_time_sec == 0 assert tpi_algo.off_time_sec == 0
assert entity.mean_cycle_power is None # no device power configured assert entity.mean_cycle_power is None # no device power configured
tpi_algo.calculate(15, 14, 5, False) tpi_algo.calculate(15, 14, 5, HVACMode.HEAT)
assert tpi_algo.on_percent == 0.4 assert tpi_algo.on_percent == 0.4
assert tpi_algo.calculated_on_percent == 0.4 assert tpi_algo.calculated_on_percent == 0.4
assert tpi_algo.on_time_sec == 120 assert tpi_algo.on_time_sec == 120
assert tpi_algo.off_time_sec == 180 assert tpi_algo.off_time_sec == 180
tpi_algo.set_security(0.1) tpi_algo.set_security(0.1)
tpi_algo.calculate(15, 14, 5, False) tpi_algo.calculate(15, 14, 5, HVACMode.HEAT)
assert tpi_algo.on_percent == 0.1 assert tpi_algo.on_percent == 0.1
assert tpi_algo.calculated_on_percent == 0.4 assert tpi_algo.calculated_on_percent == 0.4
assert tpi_algo.on_time_sec == 30 # >= minimal_activation_delay (=30) assert tpi_algo.on_time_sec == 30 # >= minimal_activation_delay (=30)
assert tpi_algo.off_time_sec == 270 assert tpi_algo.off_time_sec == 270
tpi_algo.unset_security() tpi_algo.unset_security()
tpi_algo.calculate(15, 14, 5, False) tpi_algo.calculate(15, 14, 5, HVACMode.HEAT)
assert tpi_algo.on_percent == 0.4 assert tpi_algo.on_percent == 0.4
assert tpi_algo.calculated_on_percent == 0.4 assert tpi_algo.calculated_on_percent == 0.4
assert tpi_algo.on_time_sec == 120 assert tpi_algo.on_time_sec == 120
assert tpi_algo.off_time_sec == 180 assert tpi_algo.off_time_sec == 180
# Test minimal activation delay # Test minimal activation delay
tpi_algo.calculate(15, 14.7, 15, False) tpi_algo.calculate(15, 14.7, 15, HVACMode.HEAT)
assert tpi_algo.on_percent == 0.09 assert tpi_algo.on_percent == 0.09
assert tpi_algo.calculated_on_percent == 0.09 assert tpi_algo.calculated_on_percent == 0.09
assert tpi_algo.on_time_sec == 0 assert tpi_algo.on_time_sec == 0
assert tpi_algo.off_time_sec == 300 assert tpi_algo.off_time_sec == 300
tpi_algo.set_security(0.09) tpi_algo.set_security(0.09)
tpi_algo.calculate(15, 14.7, 15, False) tpi_algo.calculate(15, 14.7, 15, HVACMode.HEAT)
assert tpi_algo.on_percent == 0.09 assert tpi_algo.on_percent == 0.09
assert tpi_algo.calculated_on_percent == 0.09 assert tpi_algo.calculated_on_percent == 0.09
assert tpi_algo.on_time_sec == 0 assert tpi_algo.on_time_sec == 0
assert tpi_algo.off_time_sec == 300 assert tpi_algo.off_time_sec == 300
tpi_algo.unset_security() tpi_algo.unset_security()
tpi_algo.calculate(25, 30, 35, True) tpi_algo.calculate(25, 30, 35, HVACMode.COOL)
assert tpi_algo.on_percent == 1 assert tpi_algo.on_percent == 1
assert tpi_algo.calculated_on_percent == 1 assert tpi_algo.calculated_on_percent == 1
assert tpi_algo.on_time_sec == 300 assert tpi_algo.on_time_sec == 300
@@ -96,9 +100,24 @@ async def test_tpi_calculation(
assert entity.mean_cycle_power is None # no device power configured assert entity.mean_cycle_power is None # no device power configured
tpi_algo.set_security(0.09) tpi_algo.set_security(0.09)
tpi_algo.calculate(25, 30, 35, True) tpi_algo.calculate(25, 30, 35, HVACMode.COOL)
assert tpi_algo.on_percent == 0.09 assert tpi_algo.on_percent == 0.09
assert tpi_algo.calculated_on_percent == 1 assert tpi_algo.calculated_on_percent == 1
assert tpi_algo.on_time_sec == 0 assert tpi_algo.on_time_sec == 0
assert tpi_algo.off_time_sec == 300 assert tpi_algo.off_time_sec == 300
assert entity.mean_cycle_power is None # no device power configured assert entity.mean_cycle_power is None # no device power configured
tpi_algo.unset_security()
# The calculated values for HVACMode.OFF are the same as for HVACMode.HEAT.
tpi_algo.calculate(15, 10, 7, HVACMode.OFF)
assert tpi_algo.on_percent == 1
assert tpi_algo.calculated_on_percent == 1
assert tpi_algo.on_time_sec == 300
assert tpi_algo.off_time_sec == 0
# If target_temp or current_temp are None, _calculated_on_percent is set to 0.
tpi_algo.calculate(15, None, 7, HVACMode.OFF)
assert tpi_algo.on_percent == 0
assert tpi_algo.calculated_on_percent == 0
assert tpi_algo.on_time_sec == 0
assert tpi_algo.off_time_sec == 300