Issue #820 - improve power shedding algorithm (#821)

* Issue #820 - improve power shedding algorithm

* Fix testu

* Release

* Fix cannot turn-off a VTherm in overpowering mode

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
This commit is contained in:
Jean-Marc Collin
2025-01-15 07:48:45 +01:00
committed by GitHub
parent 3e96247b63
commit e71d8dba86
7 changed files with 166 additions and 14 deletions

View File

@@ -398,7 +398,8 @@ async def test_bug_407(
assert entity.target_temperature == 19
assert mock_service_call.call_count >= 1
# 3. if heater is stopped (is_device_active==False) and power is over max, then overpowering should be started
# 3. Evenif heater is stopped (is_device_active==False) and power is over max, then overpowering should be started
# due to check before start heating
side_effects.add_or_update_side_effect("sensor.the_power_sensor", State("sensor.the_power_sensor", 150))
with patch(
"homeassistant.core.ServiceRegistry.async_call"
@@ -413,18 +414,18 @@ async def test_bug_407(
now = now + timedelta(seconds=30)
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
# change preset to Boost
# change preset to Comfort
await entity.async_set_preset_mode(PRESET_COMFORT)
# waits that the heater starts
# waits the eventual heater starts
await asyncio.sleep(0.1)
# simulate a refresh for central power (not necessary)
await do_central_power_refresh(hass)
# simulate a refresh for central power (not necessary because it is checked before start)
# await do_central_power_refresh(hass)
assert entity.power_manager.is_overpowering_detected is False
assert entity.power_manager.is_overpowering_detected is True
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_COMFORT
assert entity.power_manager.overpowering_state is STATE_OFF
assert entity.preset_mode is PRESET_POWER
assert entity.power_manager.overpowering_state is STATE_ON
@pytest.mark.parametrize("expected_lingering_tasks", [True])

View File

@@ -825,3 +825,132 @@ async def test_power_management_energy_over_climate(
# Test the re-increment
entity.incremente_energy()
assert entity.total_energy == 2 * 100 * 3.0 / 60
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_power_management_turn_off_while_shedding(hass: HomeAssistant, skip_hass_states_is_state, init_central_power_manager):
"""Test the Power management and that we can turn off a Vtherm that
is in overpowering state"""
temps = {
"eco": 17,
"comfort": 18,
"boost": 19,
}
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,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: True,
CONF_USE_PRESENCE_FEATURE: False,
CONF_UNDERLYING_LIST: ["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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_DEVICE_POWER: 100,
CONF_PRESET_POWER: 12,
},
)
entity: ThermostatOverSwitch = await create_thermostat(hass, entry, "climate.theoverswitchmockname", temps)
assert entity
now: datetime = NowClass.get_now(hass)
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
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.power_manager.overpowering_state is STATE_UNKNOWN
assert entity.target_temperature == 19
# make the heater heats
await send_temperature_change_event(entity, 15, now)
await send_ext_temperature_change_event(entity, 1, now)
await hass.async_block_till_done()
assert entity.power_percent > 0
side_effects = SideEffects(
{
"sensor.the_power_sensor": State("sensor.the_power_sensor", 50),
"sensor.the_max_power_sensor": State("sensor.the_max_power_sensor", 49),
},
State("unknown.entity_id", "unknown"),
)
# # fmt:off
# with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()):
# # fmt: on
# await send_power_change_event(entity, 50, datetime.now())
# # Send power max mesurement
# now = now + timedelta(seconds=30)
# VersatileThermostatAPI.get_vtherm_api()._set_now(now)
# await send_max_power_change_event(entity, 300, datetime.now())
#
# assert entity.power_manager.is_overpowering_detected is False
# # All configuration is complete and power is < power_max
# assert entity.preset_mode is PRESET_BOOST
# assert entity.power_manager.overpowering_state is STATE_OFF
# 1. Set VTherm to overpowering
# Send power max mesurement too low and HVACMode is on and device is active
# fmt:off
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()), \
patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"), \
patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on") as mock_heater_on, \
patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, \
patch("custom_components.versatile_thermostat.thermostat_switch.ThermostatOverSwitch.is_device_active", return_value="True"):
# fmt: on
now = now + timedelta(seconds=30)
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
await send_max_power_change_event(entity, 49, now)
assert entity.power_manager.is_overpowering_detected is True
# All configuration is complete and power is > power_max we switch to POWER preset
assert entity.preset_mode is PRESET_POWER
assert entity.power_manager.overpowering_state is STATE_ON
assert entity.target_temperature == 12
assert mock_heater_on.call_count == 0
assert mock_heater_off.call_count == 1
# 2. Turn-off Vtherm
# fmt:off
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()), \
patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \
patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on") as mock_heater_on, \
patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, \
patch("custom_components.versatile_thermostat.thermostat_switch.ThermostatOverSwitch.is_device_active", return_value="True"):
# fmt: on
now = now + timedelta(seconds=30)
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
await entity.async_set_hvac_mode(HVACMode.OFF)
await VersatileThermostatAPI.get_vtherm_api().central_power_manager._do_immediate_shedding()
await hass.async_block_till_done()
# VTherm is off and overpowering if off also
assert entity.hvac_mode == HVACMode.OFF
assert entity.power_manager.is_overpowering_detected is False
assert entity.preset_mode is PRESET_BOOST
assert entity.power_manager.overpowering_state is STATE_OFF
assert entity.target_temperature == 19