move AC mode config under the right configuration step (#108)

* move AC mode config under the right configuration step
This commit is contained in:
Andrea Nicotra
2023-10-21 08:31:45 +02:00
committed by GitHub
parent 043fd5f7aa
commit eb54f2826f
10 changed files with 206 additions and 99 deletions

View File

@@ -16,7 +16,6 @@
"ms-python.vscode-pylance" "ms-python.vscode-pylance"
], ],
"mounts": [ "mounts": [
"source=/Users/jmcollin/SugarSync/Projets/home-assistant/core,target=/home/vscode/core,type=bind,consistency=cached",
"source=${localWorkspaceFolder}/.devcontainer/configuration.yaml,target=/home/vscode/core/config/configuration.yaml,type=bind,consistency=cached", "source=${localWorkspaceFolder}/.devcontainer/configuration.yaml,target=/home/vscode/core/config/configuration.yaml,type=bind,consistency=cached",
"source=${localWorkspaceFolder}/custom_components,target=/home/vscode/core/config/custom_components,type=bind,consistency=cached" "source=${localWorkspaceFolder}/custom_components,target=/home/vscode/core/config/custom_components,type=bind,consistency=cached"
], ],

2
.gitignore vendored
View File

@@ -103,4 +103,6 @@ dist
# TernJS port file # TernJS port file
.tern-port .tern-port
# init file required for unittest
custom_components/__init__.py
__pycache__ __pycache__

View File

@@ -6,33 +6,45 @@
cd $HA cd $HA
echo "arguments are: "$* function get_dev() {
# Post installation of container cd /workspaces/versatile_thermostat/custom_components/versatile_thermostat/
command=$1 pip install pytest
if [ "$command" == "install" ]; then pip install -r requirements_dev.txt
echo "Running container post installation" pip install -r requirements_test.txt
script/setup sudo chown -R vscode: /home/vscode/core
fi cd -
}
if [ "$command" == "start" ]; then echo "arguments are: "$1
echo "Running container start"
hass -c ./config --debug
fi
if [ "$command" == "translations" ]; then case $1 in
echo "Running container start" start)
python3 -m script.translations develop echo "Running container start"
fi cd $HA
hass -c ./config --debug
if [ "$command" == "hassfest" ]; then ;;
echo "Running container start" dev-setup)
python3 -m script.hassfest get_dev
# python -m script.hassfest --requirements --action validate --integration-path config/custom_components/versatile_thermostat/ ;;
fi install)
echo "Running container post installation"
if [ "$command" == "restart" ]; then script/setup
echo "Killing existing container" ;;
pkill hass translations)
echo "Killing existing container" echo "Running container start"
hass -c ./config cd $HA
fi python3 -m script.translations develop
;;
hassfest)
echo "Running container start"
python3 -m script.hassfest
# python -m script.hassfest --requirements --action validate --integration-path config/custom_components/versatile_thermostat/
;;
restart)
echo "Killing existing container"
pkill hass
echo "Killing existing container"
cd $HA
hass -c ./config
;;
esac

View File

@@ -301,7 +301,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
entry_infos, entry_infos,
) )
self._ac_mode = entry_infos.get(CONF_AC_MODE) == True self._ac_mode = entry_infos.get(CONF_AC_MODE) is True
# convert entry_infos into usable attributes # convert entry_infos into usable attributes
presets = {} presets = {}
items = CONF_PRESETS_WITH_AC.items() if self._ac_mode else CONF_PRESETS.items() items = CONF_PRESETS_WITH_AC.items() if self._ac_mode else CONF_PRESETS.items()
@@ -339,7 +339,12 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._thermostat_type = entry_infos.get(CONF_THERMOSTAT_TYPE) self._thermostat_type = entry_infos.get(CONF_THERMOSTAT_TYPE)
if self._thermostat_type == CONF_THERMOSTAT_CLIMATE: if self._thermostat_type == CONF_THERMOSTAT_CLIMATE:
self._is_over_climate = True self._is_over_climate = True
for climate in [CONF_CLIMATE, CONF_CLIMATE_2, CONF_CLIMATE_3, CONF_CLIMATE_4]: for climate in [
CONF_CLIMATE,
CONF_CLIMATE_2,
CONF_CLIMATE_3,
CONF_CLIMATE_4,
]:
if entry_infos.get(climate): if entry_infos.get(climate):
self._underlyings.append( self._underlyings.append(
UnderlyingClimate( UnderlyingClimate(
@@ -769,7 +774,10 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self.async_write_ha_state() self.async_write_ha_state()
if self._prop_algorithm: if self._prop_algorithm:
self._prop_algorithm.calculate( self._prop_algorithm.calculate(
self._target_temp, self._cur_temp, self._cur_ext_temp self._target_temp,
self._cur_temp,
self._cur_ext_temp,
self._hvac_mode == HVACMode.COOL,
) )
self.hass.create_task(self._check_switch_initial_state()) self.hass.create_task(self._check_switch_initial_state())
@@ -962,8 +970,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
# Issue #114 - returns my current hvac_mode and not the underlying hvac_mode which could be different # Issue #114 - returns my current hvac_mode and not the underlying hvac_mode which could be different
# delta will be managed by climate_state_change event. # delta will be managed by climate_state_change event.
# if self._is_over_climate: # if self._is_over_climate:
# if one not OFF -> return it # if one not OFF -> return it
# else OFF # else OFF
# for under in self._underlyings: # for under in self._underlyings:
# if (mode := under.hvac_mode) not in [HVACMode.OFF] # if (mode := under.hvac_mode) not in [HVACMode.OFF]
# return mode # return mode
@@ -983,7 +991,10 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
# else OFF # else OFF
one_idle = False one_idle = False
for under in self._underlyings: for under in self._underlyings:
if (action := under.hvac_action) not in [HVACAction.IDLE, HVACAction.OFF]: if (action := under.hvac_action) not in [
HVACAction.IDLE,
HVACAction.OFF,
]:
return action return action
if under.hvac_action == HVACAction.IDLE: if under.hvac_action == HVACAction.IDLE:
one_idle = True one_idle = True
@@ -995,6 +1006,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
return HVACAction.OFF return HVACAction.OFF
if not self._is_device_active: if not self._is_device_active:
return HVACAction.IDLE return HVACAction.IDLE
if self._hvac_mode == HVACMode.COOL:
return HVACAction.COOLING
return HVACAction.HEATING return HVACAction.HEATING
@property @property
@@ -1066,7 +1079,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
@property @property
def mean_cycle_power(self) -> float | None: def mean_cycle_power(self) -> float | None:
"""Returns tne mean power consumption during the cycle""" """Returns the mean power consumption during the cycle"""
if not self._device_power or self._is_over_climate: if not self._device_power or self._is_over_climate:
return None return None
@@ -1613,7 +1626,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
"""Prevent the device from keep running if HVAC_MODE_OFF.""" """Prevent the device from keep running if HVAC_MODE_OFF."""
_LOGGER.debug("%s - Calling _check_switch_initial_state", self) _LOGGER.debug("%s - Calling _check_switch_initial_state", self)
# We need to do the same check for over_climate underlyings # We need to do the same check for over_climate underlyings
#if self.is_over_climate: # if self.is_over_climate:
# return # return
for under in self._underlyings: for under in self._underlyings:
await under.check_initial_state(self._hvac_mode) await under.check_initial_state(self._hvac_mode)
@@ -1632,16 +1645,16 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
@callback @callback
async def _async_climate_changed(self, event): async def _async_climate_changed(self, event):
"""Handle unerdlying climate state changes. """Handle unerdlying climate state changes.
This method takes the underlying values and update the VTherm with them. This method takes the underlying values and update the VTherm with them.
To avoid loops (issues #121 #101 #95 #99), we discard the event if it is received To avoid loops (issues #121 #101 #95 #99), we discard the event if it is received
less than 10 sec after the last command. What we want here is to take the values less than 10 sec after the last command. What we want here is to take the values
from underlyings ONLY if someone have change directly on the underlying and not from underlyings ONLY if someone have change directly on the underlying and not
as a return of the command. The only thing we take all the time is the HVACAction as a return of the command. The only thing we take all the time is the HVACAction
which is important for feedaback and which cannot generates loops. which is important for feedaback and which cannot generates loops.
""" """
async def end_climate_changed(changes): async def end_climate_changed(changes):
""" To end the event management""" """To end the event management"""
if changes: if changes:
self.async_write_ha_state() self.async_write_ha_state()
self.update_custom_attributes() self.update_custom_attributes()
@@ -1667,15 +1680,25 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
else None else None
) )
old_state_date_changed = old_state.last_changed if old_state and old_state.last_changed else None old_state_date_changed = (
old_state_date_updated = old_state.last_updated if old_state and old_state.last_updated else None old_state.last_changed if old_state and old_state.last_changed else None
new_state_date_changed = new_state.last_changed if new_state and new_state.last_changed else None )
new_state_date_updated = new_state.last_updated if new_state and new_state.last_updated else None old_state_date_updated = (
old_state.last_updated if old_state and old_state.last_updated else None
)
new_state_date_changed = (
new_state.last_changed if new_state and new_state.last_changed else None
)
new_state_date_updated = (
new_state.last_updated if new_state and new_state.last_updated else None
)
# Issue 99 - some AC turn hvac_mode=cool and hvac_action=idle when sending a HVACMode_OFF command # Issue 99 - some AC turn hvac_mode=cool and hvac_action=idle when sending a HVACMode_OFF command
# Issue 114 - Remove this because hvac_mode is now managed by local _hvac_mode and use idle action as is # Issue 114 - Remove this because hvac_mode is now managed by local _hvac_mode and use idle action as is
#if self._hvac_mode == HVACMode.OFF and new_hvac_action == HVACAction.IDLE: # if self._hvac_mode == HVACMode.OFF and new_hvac_action == HVACAction.IDLE:
# _LOGGER.debug("The underlying switch to idle instead of OFF. We will consider it as OFF") # _LOGGER.debug(
# "The underlying switch to idle instead of OFF. We will consider it as OFF"
# )
# new_hvac_mode = HVACMode.OFF # new_hvac_mode = HVACMode.OFF
_LOGGER.info( _LOGGER.info(
@@ -1687,7 +1710,17 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
old_hvac_action, old_hvac_action,
) )
_LOGGER.debug("%s - last_change_time=%s old_state_date_changed=%s old_state_date_updated=%s new_state_date_changed=%s new_state_date_updated=%s", self, self._last_change_time, old_state_date_changed, old_state_date_updated, new_state_date_changed, new_state_date_updated) if new_hvac_mode in [
HVACMode.OFF,
HVACMode.HEAT,
HVACMode.COOL,
HVACMode.HEAT_COOL,
HVACMode.DRY,
HVACMode.AUTO,
HVACMode.FAN_ONLY,
None,
]:
self._hvac_mode = new_hvac_mode
# Interpretation of hvac action # Interpretation of hvac action
HVAC_ACTION_ON = [ # pylint: disable=invalid-name HVAC_ACTION_ON = [ # pylint: disable=invalid-name
@@ -1733,21 +1766,27 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
if new_state_date_updated and self._last_change_time: if new_state_date_updated and self._last_change_time:
delta = (new_state_date_updated - self._last_change_time).total_seconds() delta = (new_state_date_updated - self._last_change_time).total_seconds()
if delta < 10: if delta < 10:
_LOGGER.info("%s - underlying event is received less than 10 sec after command. Forget it to avoid loop", self _LOGGER.info(
"%s - underlying event is received less than 10 sec after command. Forget it to avoid loop",
self,
) )
await end_climate_changed(changes) await end_climate_changed(changes)
return return
if new_hvac_mode in [ if (
HVACMode.OFF, new_hvac_mode
HVACMode.HEAT, in [
HVACMode.COOL, HVACMode.OFF,
HVACMode.HEAT_COOL, HVACMode.HEAT,
HVACMode.DRY, HVACMode.COOL,
HVACMode.AUTO, HVACMode.HEAT_COOL,
HVACMode.FAN_ONLY, HVACMode.DRY,
None HVACMode.AUTO,
] and self._hvac_mode != new_hvac_mode: HVACMode.FAN_ONLY,
None,
]
and self._hvac_mode != new_hvac_mode
):
changes = True changes = True
self._hvac_mode = new_hvac_mode self._hvac_mode = new_hvac_mode
# Update all underlyings state # Update all underlyings state
@@ -1757,15 +1796,27 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
if not changes: if not changes:
# try to manage new target temperature set if state # try to manage new target temperature set if state
_LOGGER.debug("Do temperature check. temperature is %s, new_state.attributes is %s", self.target_temperature, new_state.attributes) _LOGGER.debug(
if self._is_over_climate and new_state.attributes and (new_target_temp := new_state.attributes.get("temperature")) and new_target_temp != self.target_temperature: "Do temperature check. temperature is %s, new_state.attributes is %s",
_LOGGER.info("%s - Target temp in underlying have change to %s", self, new_target_temp) self.target_temperature,
await self.async_set_temperature(temperature = new_target_temp) new_state.attributes,
)
if (
self._is_over_climate
and new_state.attributes
and (new_target_temp := new_state.attributes.get("temperature"))
and new_target_temp != self.target_temperature
):
_LOGGER.info(
"%s - Target temp in underlying have change to %s",
self,
new_target_temp,
)
await self.async_set_temperature(temperature=new_target_temp)
changes = True changes = True
await end_climate_changed(changes) await end_climate_changed(changes)
@callback @callback
async def _async_update_temp(self, state: State): async def _async_update_temp(self, state: State):
"""Update thermostat with latest state from sensor.""" """Update thermostat with latest state from sensor."""
@@ -2208,13 +2259,19 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
) )
# Issue 99 - a climate is regulated by the device itself and not by VTherm. So a VTherm should never be in security ! # Issue 99 - a climate is regulated by the device itself and not by VTherm. So a VTherm should never be in security !
shouldClimateBeInSecurity = False # temp_cond and climate_cond shouldClimateBeInSecurity = False # temp_cond and climate_cond
shouldSwitchBeInSecurity = temp_cond and switch_cond shouldSwitchBeInSecurity = temp_cond and switch_cond
shouldBeInSecurity = shouldClimateBeInSecurity or shouldSwitchBeInSecurity shouldBeInSecurity = shouldClimateBeInSecurity or shouldSwitchBeInSecurity
shouldStartSecurity = mode_cond and not self._security_state and shouldBeInSecurity shouldStartSecurity = (
mode_cond and not self._security_state and shouldBeInSecurity
)
# attr_preset_mode is not necessary normaly. It is just here to be sure # attr_preset_mode is not necessary normaly. It is just here to be sure
shouldStopSecurity = self._security_state and not shouldBeInSecurity and self._attr_preset_mode == PRESET_SECURITY shouldStopSecurity = (
self._security_state
and not shouldBeInSecurity
and self._attr_preset_mode == PRESET_SECURITY
)
# Logging and event # Logging and event
if shouldStartSecurity: if shouldStartSecurity:
@@ -2376,7 +2433,10 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
_LOGGER.debug("%s - recalculate all", self) _LOGGER.debug("%s - recalculate all", self)
if not self._is_over_climate: if not self._is_over_climate:
self._prop_algorithm.calculate( self._prop_algorithm.calculate(
self._target_temp, self._cur_temp, self._cur_ext_temp self._target_temp,
self._cur_temp,
self._cur_ext_temp,
self._hvac_mode == HVACMode.COOL,
) )
self.update_custom_attributes() self.update_custom_attributes()
self.async_write_ha_state() self.async_write_ha_state()
@@ -2463,18 +2523,18 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
"max_power_sensor_entity_id": self._max_power_sensor_entity_id, "max_power_sensor_entity_id": self._max_power_sensor_entity_id,
} }
if self._is_over_climate: if self._is_over_climate:
self._attr_extra_state_attributes["underlying_climate_0"] = self._underlyings[ self._attr_extra_state_attributes[
0 "underlying_climate_0"
].entity_id ] = self._underlyings[0].entity_id
self._attr_extra_state_attributes["underlying_climate_1"] = self._underlyings[ self._attr_extra_state_attributes["underlying_climate_1"] = (
1 self._underlyings[1].entity_id if len(self._underlyings) > 1 else None
].entity_id if len(self._underlyings) > 1 else None )
self._attr_extra_state_attributes["underlying_climate_2"] = self._underlyings[ self._attr_extra_state_attributes["underlying_climate_2"] = (
2 self._underlyings[2].entity_id if len(self._underlyings) > 2 else None
].entity_id if len(self._underlyings) > 2 else None )
self._attr_extra_state_attributes["underlying_climate_3"] = self._underlyings[ self._attr_extra_state_attributes["underlying_climate_3"] = (
3 self._underlyings[3].entity_id if len(self._underlyings) > 3 else None
].entity_id if len(self._underlyings) > 3 else None )
self._attr_extra_state_attributes[ self._attr_extra_state_attributes[
"start_hvac_action_date" "start_hvac_action_date"
@@ -2566,7 +2626,9 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
# If the changed preset is active, change the current temperature # If the changed preset is active, change the current temperature
# Issue #119 - reload new preset temperature also in ac mode # Issue #119 - reload new preset temperature also in ac mode
if preset.startswith(self._attr_preset_mode): if preset.startswith(self._attr_preset_mode):
await self._async_set_preset_mode_internal(preset.rstrip(PRESET_AC_SUFFIX), force=True) await self._async_set_preset_mode_internal(
preset.rstrip(PRESET_AC_SUFFIX), force=True
)
await self._async_control_heating(force=True) await self._async_control_heating(force=True)
async def service_set_security(self, delay_min, min_on_percent, default_on_percent): async def service_set_security(self, delay_min, min_on_percent, default_on_percent):

View File

@@ -227,6 +227,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
PROPORTIONAL_FUNCTION_TPI, PROPORTIONAL_FUNCTION_TPI,
] ]
), ),
vol.Optional(CONF_AC_MODE, default=False): cv.boolean,
} }
) )
@@ -244,7 +245,6 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
vol.Optional(CONF_CLIMATE_4): selector.EntitySelector( vol.Optional(CONF_CLIMATE_4): selector.EntitySelector(
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN), selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN),
), ),
vol.Optional(CONF_AC_MODE, default=False): cv.boolean,
} }
) )

View File

@@ -45,19 +45,33 @@ class PropAlgorithm:
self._default_on_percent = 0 self._default_on_percent = 0
def calculate( def calculate(
self, target_temp: float, current_temp: float, ext_current_temp: float self,
target_temp: float,
current_temp: float,
ext_current_temp: float,
cooling=False,
): ):
"""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( _LOGGER.warning(
"Proportional algorithm: calculation is not possible cause target_temp or current_temp is null. Heating 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:
delta_temp = target_temp - current_temp if cooling:
delta_ext_temp = ( delta_temp = current_temp - target_temp
target_temp - ext_current_temp if ext_current_temp is not None else 0 delta_ext_temp = (
) ext_current_temp
if ext_current_temp is not None
else 0 - target_temp
)
else:
delta_temp = target_temp - current_temp
delta_ext_temp = (
target_temp - ext_current_temp
if ext_current_temp is not None
else 0
)
if self._function == PROPORTIONAL_FUNCTION_TPI: if self._function == PROPORTIONAL_FUNCTION_TPI:
self._calculated_on_percent = ( self._calculated_on_percent = (

View File

@@ -97,6 +97,7 @@ MOCK_TH_OVER_CLIMATE_USER_CONFIG = {
MOCK_TH_OVER_SWITCH_TYPE_CONFIG = { MOCK_TH_OVER_SWITCH_TYPE_CONFIG = {
CONF_HEATER: "switch.mock_switch", CONF_HEATER: "switch.mock_switch",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_AC_MODE: False,
} }
MOCK_TH_OVER_4SWITCH_TYPE_CONFIG = { MOCK_TH_OVER_4SWITCH_TYPE_CONFIG = {
@@ -105,6 +106,7 @@ MOCK_TH_OVER_4SWITCH_TYPE_CONFIG = {
CONF_HEATER_3: "switch.mock_4switch2", CONF_HEATER_3: "switch.mock_4switch2",
CONF_HEATER_4: "switch.mock_4switch3", CONF_HEATER_4: "switch.mock_4switch3",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_AC_MODE: False,
} }
MOCK_TH_OVER_SWITCH_TPI_CONFIG = { MOCK_TH_OVER_SWITCH_TPI_CONFIG = {
@@ -114,7 +116,6 @@ MOCK_TH_OVER_SWITCH_TPI_CONFIG = {
MOCK_TH_OVER_CLIMATE_TYPE_CONFIG = { MOCK_TH_OVER_CLIMATE_TYPE_CONFIG = {
CONF_CLIMATE: "climate.mock_climate", CONF_CLIMATE: "climate.mock_climate",
CONF_AC_MODE: False,
} }
MOCK_PRESETS_CONFIG = { MOCK_PRESETS_CONFIG = {

View File

@@ -378,6 +378,7 @@ async def test_user_config_flow_over_4_switches(
CONF_HEATER_3: "switch.mock_switch3", CONF_HEATER_3: "switch.mock_switch3",
CONF_HEATER_4: "switch.mock_switch4", CONF_HEATER_4: "switch.mock_switch4",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_AC_MODE: False,
} }
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(

View File

@@ -5,7 +5,7 @@ from .commons import * # pylint: disable=wildcard-import, unused-wildcard-impor
@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])
async def test_tpi_calculation(hass: HomeAssistant, skip_hass_states_is_state): async def test_tpi_calculation(hass: HomeAssistant, skip_hass_states_is_state: None):
"""Test the TPI calculation""" """Test the TPI calculation"""
entry = MockConfigEntry( entry = MockConfigEntry(
@@ -50,36 +50,52 @@ async def test_tpi_calculation(hass: HomeAssistant, skip_hass_states_is_state):
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) tpi_algo.calculate(15, 14, 5, False)
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) tpi_algo.calculate(15, 14, 5, False)
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) tpi_algo.calculate(15, 14, 5, False)
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) tpi_algo.calculate(15, 14.7, 15, False)
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) tpi_algo.calculate(15, 14.7, 15, False)
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.calculate(25, 30, 35, True)
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
assert entity.mean_cycle_power is None # no device power configured
tpi_algo.set_security(0.09)
tpi_algo.calculate(25, 30, 35, True)
assert tpi_algo.on_percent == 0.09
assert tpi_algo.calculated_on_percent == 1
assert tpi_algo.on_time_sec == 0
assert tpi_algo.off_time_sec == 300
assert entity.mean_cycle_power is None # no device power configured

View File

@@ -246,7 +246,7 @@ class UnderlyingSwitch(UnderlyingEntity):
return return
# If we should heat, starts the cycle with delay # If we should heat, starts the cycle with delay
if self._hvac_mode == HVACMode.HEAT and on_time_sec > 0: if self._hvac_mode in [HVACMode.HEAT, HVACMode.COOL] and on_time_sec > 0:
# Starts the cycle after the initial delay # Starts the cycle after the initial delay
self._async_cancel_cycle = self.call_later( self._async_cancel_cycle = self.call_later(
self._hass, self._initial_delay_sec, self._turn_on_later self._hass, self._initial_delay_sec, self._turn_on_later