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
pip install -r requirements_test.txt
sudo chown -R vscode: /home/vscode/core
cd -
}
echo "arguments are: "$1
case $1 in
start)
echo "Running container start"
cd $HA
hass -c ./config --debug
;;
dev-setup)
get_dev
;;
install)
echo "Running container post installation" echo "Running container post installation"
script/setup script/setup
fi ;;
translations)
if [ "$command" == "start" ]; then
echo "Running container start"
hass -c ./config --debug
fi
if [ "$command" == "translations" ]; then
echo "Running container start" echo "Running container start"
cd $HA
python3 -m script.translations develop python3 -m script.translations develop
fi ;;
hassfest)
if [ "$command" == "hassfest" ]; then
echo "Running container start" echo "Running container start"
python3 -m script.hassfest python3 -m script.hassfest
# python -m script.hassfest --requirements --action validate --integration-path config/custom_components/versatile_thermostat/ # python -m script.hassfest --requirements --action validate --integration-path config/custom_components/versatile_thermostat/
fi ;;
restart)
if [ "$command" == "restart" ]; then
echo "Killing existing container" echo "Killing existing container"
pkill hass pkill hass
echo "Killing existing container" echo "Killing existing container"
cd $HA
hass -c ./config hass -c ./config
fi ;;
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())
@@ -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)
@@ -1641,7 +1654,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
""" """
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,12 +1766,16 @@ 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 (
new_hvac_mode
in [
HVACMode.OFF, HVACMode.OFF,
HVACMode.HEAT, HVACMode.HEAT,
HVACMode.COOL, HVACMode.COOL,
@@ -1746,8 +1783,10 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
HVACMode.DRY, HVACMode.DRY,
HVACMode.AUTO, HVACMode.AUTO,
HVACMode.FAN_ONLY, HVACMode.FAN_ONLY,
None None,
] and self._hvac_mode != new_hvac_mode: ]
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."""
@@ -2212,9 +2263,15 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
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,18 +45,32 @@ 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:
if cooling:
delta_temp = current_temp - target_temp
delta_ext_temp = (
ext_current_temp
if ext_current_temp is not None
else 0 - target_temp
)
else: else:
delta_temp = target_temp - current_temp delta_temp = target_temp - current_temp
delta_ext_temp = ( delta_ext_temp = (
target_temp - ext_current_temp if ext_current_temp is not None else 0 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:

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