Add temporal filter for calculate_shedding

Add restore overpowering state at startup
This commit is contained in:
Jean-Marc Collin
2025-01-05 10:30:34 +00:00
parent 6bdcecefac
commit 81231f977c
18 changed files with 170 additions and 151 deletions

View File

@@ -751,6 +751,7 @@ async def send_power_change_event(entity: BaseThermostat, new_power, date, sleep
)
vtherm_api = VersatileThermostatAPI.get_vtherm_api()
await vtherm_api.central_power_manager._power_sensor_changed(power_event)
await vtherm_api.central_power_manager._do_immediate_shedding()
if sleep:
await entity.hass.async_block_till_done()
@@ -778,6 +779,7 @@ async def send_max_power_change_event(
)
vtherm_api = VersatileThermostatAPI.get_vtherm_api()
await vtherm_api.central_power_manager._max_power_sensor_changed(power_event)
await vtherm_api.central_power_manager._do_immediate_shedding()
if sleep:
await entity.hass.async_block_till_done()

View File

@@ -192,13 +192,13 @@ async def test_overpowering_binary_sensors(
assert overpowering_binary_sensor.state == STATE_ON
# set max power to a low value
side_effects.add_or_update_side_effect("sensor.the_max_power_sensor", State("sensor.the_max_power_sensor", 201))
side_effects.add_or_update_side_effect("sensor.the_max_power_sensor", State("sensor.the_max_power_sensor", 251))
# fmt:off
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()):
# fmt: on
now = now + timedelta(seconds=30)
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
await send_max_power_change_event(entity, 201, now)
await send_max_power_change_event(entity, 251, now)
assert entity.power_manager.is_overpowering_detected is False
assert entity.power_manager.overpowering_state is STATE_OFF
# Simulate the event reception

View File

@@ -59,7 +59,7 @@ async def test_central_power_manager_init(
assert central_power_manager.power_temperature == power_temp
# 3. start listening
central_power_manager.start_listening()
await central_power_manager.start_listening()
assert len(central_power_manager._active_listener) == (2 if is_configured else 0)
# 4. stop listening
@@ -311,6 +311,54 @@ async def test_central_power_manageer_find_vtherms(
# init vtherm1 to False
{"vtherm3": False, "vtherm2": False, "vtherm1": False},
),
# Un-shedding only (will be taken in reverse order)
(
1000,
2000,
[
# should be not unshedded (too much power will be added)
{
"name": "vtherm1",
"device_power": 800,
"is_device_active": False,
"is_over_climate": False,
"nb_underlying_entities": 1,
"on_percent": 1,
"is_overpowering_detected": True,
"overpowering_state": STATE_ON,
},
# already stay unshedded cause already unshedded
{
"name": "vtherm2",
"device_power": 100,
"is_device_active": True,
"is_over_climate": True,
"is_overpowering_detected": False,
"overpowering_state": STATE_OFF,
},
# should be unshedded
{
"name": "vtherm3",
"device_power": 200,
"is_device_active": False,
"is_over_climate": True,
"is_overpowering_detected": True,
"overpowering_state": STATE_ON,
},
# should be unshedded
{
"name": "vtherm4",
"device_power": 300,
"is_device_active": False,
"is_over_climate": False,
"nb_underlying_entities": 1,
"on_percent": 1,
"is_overpowering_detected": True,
"overpowering_state": STATE_ON,
},
],
{"vtherm4": False, "vtherm3": False},
),
# Shedding
(
2000,
@@ -391,65 +439,6 @@ async def test_central_power_manageer_find_vtherms(
],
{"vtherm1": True, "vtherm2": True, "vtherm3": True, "vtherm6": True},
),
# Un-shedding only (will be taken in reverse order)
(
1000,
2000,
[
# should be not unshedded (we have enough)
{
"name": "vtherm0",
"device_power": 800,
"is_device_active": False,
"is_over_climate": False,
"nb_underlying_entities": 1,
"on_percent": 1,
"is_overpowering_detected": True,
"overpowering_state": STATE_ON,
},
# should be unshedded
{
"name": "vtherm1",
"device_power": 800,
"is_device_active": False,
"is_over_climate": False,
"nb_underlying_entities": 1,
"on_percent": 1,
"is_overpowering_detected": True,
"overpowering_state": STATE_ON,
},
# already stay unshedded cause already unshedded
{
"name": "vtherm2",
"device_power": 1100,
"is_device_active": True,
"is_over_climate": True,
"is_overpowering_detected": False,
"overpowering_state": STATE_OFF,
},
# should be unshedded
{
"name": "vtherm3",
"device_power": 200,
"is_device_active": False,
"is_over_climate": True,
"is_overpowering_detected": True,
"overpowering_state": STATE_ON,
},
# should be unshedded
{
"name": "vtherm4",
"device_power": 300,
"is_device_active": False,
"is_over_climate": False,
"nb_underlying_entities": 1,
"on_percent": 1,
"is_overpowering_detected": True,
"overpowering_state": STATE_ON,
},
],
{"vtherm4": False, "vtherm3": False, "vtherm1": False},
),
],
)
# @pytest.mark.skip
@@ -553,7 +542,7 @@ async def test_central_power_manager_power_event(
assert central_power_manager.power_temperature == 13
# 3. start listening (not really useful but don't eat bread)
central_power_manager.start_listening()
await central_power_manager.start_listening()
assert len(central_power_manager._active_listener) == 2
now: datetime = NowClass.get_now(hass)
@@ -582,6 +571,9 @@ async def test_central_power_manager_power_event(
"old_state": State("sensor.power_entity_id", STATE_UNAVAILABLE),
}))
if nb_call > 0:
await central_power_manager._do_immediate_shedding()
expected_power = power if isinstance(power, (int, float)) else -999
assert central_power_manager.current_power == expected_power
assert mock_calculate_shedding.call_count == nb_call
@@ -603,8 +595,11 @@ async def test_central_power_manager_power_event(
"old_state": State("sensor.power_entity_id", STATE_UNAVAILABLE),
}))
if nb_call > 0:
await central_power_manager._do_immediate_shedding()
assert central_power_manager.current_power == expected_power
assert mock_calculate_shedding.call_count == (nb_call if dsecs >= 20 else 0)
assert mock_calculate_shedding.call_count == nb_call
@pytest.mark.parametrize(
@@ -645,7 +640,7 @@ async def test_central_power_manager_max_power_event(
assert central_power_manager.power_temperature == 13
# 3. start listening (not really useful but don't eat bread)
central_power_manager.start_listening()
await central_power_manager.start_listening()
assert len(central_power_manager._active_listener) == 2
now: datetime = NowClass.get_now(hass)
@@ -676,6 +671,9 @@ async def test_central_power_manager_max_power_event(
"old_state": State("sensor.max_power_entity_id", STATE_UNAVAILABLE),
}))
if nb_call > 0:
await central_power_manager._do_immediate_shedding()
expected_power = max_power if isinstance(max_power, (int, float)) else -999
assert central_power_manager.current_max_power == expected_power
assert mock_calculate_shedding.call_count == nb_call
@@ -697,5 +695,8 @@ async def test_central_power_manager_max_power_event(
"old_state": State("sensor.max_power_entity_id", STATE_UNAVAILABLE),
}))
if nb_call > 0:
await central_power_manager._do_immediate_shedding()
assert central_power_manager.current_max_power == expected_power
assert mock_calculate_shedding.call_count == (nb_call if dsecs >= 20 else 0)
assert mock_calculate_shedding.call_count == nb_call

View File

@@ -90,7 +90,7 @@ async def test_motion_feature_manager_refresh(
assert custom_attributes["motion_off_delay_sec"] == 30
# 3. start listening
motion_manager.start_listening()
await motion_manager.start_listening()
assert motion_manager.is_configured is True
assert motion_manager.motion_state == STATE_UNKNOWN
assert motion_manager.is_motion_detected is False
@@ -198,7 +198,7 @@ async def test_motion_feature_manager_event(
CONF_NO_MOTION_PRESET: PRESET_ECO,
}
)
motion_manager.start_listening()
await motion_manager.start_listening()
# 2. test _motion_sensor_changed with the parametrized
# fmt: off

View File

@@ -98,7 +98,7 @@ async def test_power_feature_manager(
}
)
power_manager.start_listening()
await power_manager.start_listening()
assert power_manager.is_configured is True
assert power_manager.overpowering_state == STATE_UNKNOWN
@@ -117,7 +117,7 @@ async def test_power_feature_manager(
assert custom_attributes["current_max_power"] is None
# 3. start listening
power_manager.start_listening()
await power_manager.start_listening()
assert power_manager.is_configured is True
assert power_manager.overpowering_state == STATE_UNKNOWN
@@ -199,7 +199,7 @@ async def test_power_feature_manager_set_overpowering(
}
)
power_manager.start_listening()
await power_manager.start_listening()
assert power_manager.is_configured is True
assert power_manager.overpowering_state == STATE_UNKNOWN
@@ -557,6 +557,7 @@ async def test_power_management_hvac_on(
# Send power mesurement low to unset power preset
side_effects.add_or_update_side_effect("sensor.the_power_sensor", State("sensor.the_power_sensor", 48))
side_effects.add_or_update_side_effect("sensor.the_max_power_sensor", State("sensor.the_max_power_sensor", 149))
# 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, \
@@ -583,7 +584,7 @@ async def test_power_management_hvac_on(
"type": "end",
"current_power": 48,
"device_power": 100,
"current_max_power": 49,
"current_max_power": 149,
},
),
],

View File

@@ -75,7 +75,7 @@ async def test_presence_feature_manager(
assert custom_attributes["is_presence_configured"] is True
# 3. start listening
presence_manager.start_listening()
await presence_manager.start_listening()
assert presence_manager.is_configured is True
assert presence_manager.presence_state == STATE_UNKNOWN
assert presence_manager.is_absence_detected is False

View File

@@ -170,7 +170,7 @@ async def test_window_feature_manager_refresh_sensor_action_turn_off(
)
# 3. start listening
window_manager.start_listening()
await window_manager.start_listening()
assert window_manager.is_configured is True
assert window_manager.window_state == STATE_UNKNOWN
assert window_manager.window_auto_state == STATE_UNAVAILABLE
@@ -288,7 +288,7 @@ async def test_window_feature_manager_refresh_sensor_action_frost_only(
)
# 3. start listening
window_manager.start_listening()
await window_manager.start_listening()
assert window_manager.is_configured is True
assert window_manager.window_state == STATE_UNKNOWN
assert window_manager.window_auto_state == STATE_UNAVAILABLE
@@ -408,7 +408,7 @@ async def test_window_feature_manager_sensor_event_action_turn_off(
)
# 3. start listening
window_manager.start_listening()
await window_manager.start_listening()
assert len(window_manager._active_listener) == 1
# 4. test refresh with the parametrized
@@ -535,7 +535,7 @@ async def test_window_feature_manager_event_sensor_action_frost_only(
)
# 3. start listening
window_manager.start_listening()
await window_manager.start_listening()
# 4. test refresh with the parametrized
# fmt:off
@@ -660,7 +660,7 @@ async def test_window_feature_manager_window_auto(
}
)
assert window_manager.is_window_auto_configured is True
window_manager.start_listening()
await window_manager.start_listening()
# 2. Call manage window auto
tz = get_tz(hass) # pylint: disable=invalid-name