enhance the overpowering algo if current_power > max_power
This commit is contained in:
@@ -54,6 +54,7 @@
|
||||
"python.analysis.autoSearchPaths": true,
|
||||
"pylint.lintOnChange": false,
|
||||
"python.formatting.provider": "black",
|
||||
"python.formatting.blackArgs": ["--line-length", "180"],
|
||||
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
|
||||
"editor.formatOnPaste": false,
|
||||
"editor.formatOnSave": true,
|
||||
|
||||
@@ -48,10 +48,7 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
||||
"""Gets the configuration parameters"""
|
||||
central_config = self._vtherm_api.find_central_configuration()
|
||||
if not central_config:
|
||||
_LOGGER.info(
|
||||
"%s - No central configuration is found. Power management will be deactivated.",
|
||||
self,
|
||||
)
|
||||
_LOGGER.info("No central configuration is found. Power management will be deactivated")
|
||||
return
|
||||
|
||||
self._power_sensor_entity_id = entry_infos.get(CONF_POWER_SENSOR)
|
||||
@@ -69,10 +66,7 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
||||
):
|
||||
self._is_configured = True
|
||||
else:
|
||||
_LOGGER.info(
|
||||
"%s - Power management is not fully configured and will be deactivated",
|
||||
self,
|
||||
)
|
||||
_LOGGER.info("Power management is not fully configured and will be deactivated")
|
||||
|
||||
def start_listening(self):
|
||||
"""Start listening the power sensor"""
|
||||
@@ -100,14 +94,14 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
||||
@callback
|
||||
async def _power_sensor_changed(self, event: Event[EventStateChangedData]):
|
||||
"""Handle power changes."""
|
||||
_LOGGER.debug("Thermostat %s - Receive new Power event", self)
|
||||
_LOGGER.debug("Receive new Power event")
|
||||
_LOGGER.debug(event)
|
||||
await self.refresh_state()
|
||||
|
||||
@callback
|
||||
async def _max_power_sensor_changed(self, event: Event[EventStateChangedData]):
|
||||
"""Handle power max changes."""
|
||||
_LOGGER.debug("Thermostat %s - Receive new Power Max event", self.name)
|
||||
_LOGGER.debug("Receive new Power Max event")
|
||||
_LOGGER.debug(event)
|
||||
await self.refresh_state()
|
||||
|
||||
@@ -122,11 +116,7 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
||||
new_state := get_safe_float(self._hass, self._power_sensor_entity_id)
|
||||
) is not None:
|
||||
self._current_power = new_state
|
||||
_LOGGER.debug(
|
||||
"%s - Current power have been retrieved: %.3f",
|
||||
self,
|
||||
self._current_power,
|
||||
)
|
||||
_LOGGER.debug("Current power have been retrieved: %.3f", self._current_power)
|
||||
ret = True
|
||||
|
||||
# Try to acquire power max
|
||||
@@ -136,11 +126,7 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
||||
)
|
||||
) is not None:
|
||||
self._current_max_power = new_state
|
||||
_LOGGER.debug(
|
||||
"%s - Current power max have been retrieved: %.3f",
|
||||
self,
|
||||
self._current_max_power,
|
||||
)
|
||||
_LOGGER.debug("Current power max have been retrieved: %.3f", self._current_max_power)
|
||||
ret = True
|
||||
|
||||
# check if we need to re-calculate shedding
|
||||
@@ -159,70 +145,68 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
||||
|
||||
async def calculate_shedding(self):
|
||||
"""Do the shedding calculation and set/unset VTherm into overpowering state"""
|
||||
if (
|
||||
not self.is_configured
|
||||
or not self.current_max_power
|
||||
or not self.current_power
|
||||
):
|
||||
if not self.is_configured or self.current_max_power is None or self.current_power is None:
|
||||
return
|
||||
|
||||
# Find all VTherms
|
||||
vtherms_sorted = self.find_all_vtherm_with_power_management_sorted_by_dtemp()
|
||||
available_power = self.current_max_power - self.current_power
|
||||
vtherms_sorted = self.find_all_vtherm_with_power_management_sorted_by_dtemp()
|
||||
|
||||
total_affected_power = 0
|
||||
force_overpowering = False
|
||||
|
||||
for vtherm in vtherms_sorted:
|
||||
device_power = vtherm.power_manager.device_power
|
||||
if vtherm.is_device_active:
|
||||
power_consumption_max = 0
|
||||
else:
|
||||
if vtherm.is_over_climate:
|
||||
power_consumption_max = device_power
|
||||
else:
|
||||
power_consumption_max = max(
|
||||
device_power / vtherm.nb_underlying_entities,
|
||||
device_power * vtherm.proportional_algorithm.on_percent,
|
||||
)
|
||||
|
||||
# shedding only
|
||||
if available_power < 0:
|
||||
_LOGGER.debug(
|
||||
"%s - vtherm %s power_consumption_max is %s (device_power=%s, overclimate=%s)",
|
||||
self,
|
||||
vtherm.name,
|
||||
power_consumption_max,
|
||||
device_power,
|
||||
vtherm.is_over_climate,
|
||||
)
|
||||
if force_overpowering or (
|
||||
total_affected_power + power_consumption_max >= available_power
|
||||
):
|
||||
_LOGGER.debug(
|
||||
"%s - vtherm %s should be in overpowering state", self, vtherm.name
|
||||
)
|
||||
if not vtherm.power_manager.is_overpowering_detected:
|
||||
# To force all others vtherms to be in overpowering
|
||||
force_overpowering = True
|
||||
await vtherm.power_manager.set_overpowering(
|
||||
True, power_consumption_max
|
||||
)
|
||||
else:
|
||||
total_affected_power += power_consumption_max
|
||||
# Always set to false to init the state
|
||||
_LOGGER.debug(
|
||||
"%s - vtherm %s should not be in overpowering state",
|
||||
self,
|
||||
vtherm.name,
|
||||
)
|
||||
await vtherm.power_manager.set_overpowering(False)
|
||||
|
||||
_LOGGER.debug(
|
||||
"%s - after vtherm %s total_affected_power=%s, available_power=%s",
|
||||
self,
|
||||
vtherm.name,
|
||||
total_affected_power,
|
||||
"The available power is is < 0 (%s). Set overpowering only for list: %s",
|
||||
available_power,
|
||||
vtherms_sorted,
|
||||
)
|
||||
# we will set overpowering for the nearest target temp first
|
||||
total_power_gain = 0
|
||||
|
||||
for vtherm in vtherms_sorted:
|
||||
device_power = vtherm.power_manager.device_power
|
||||
if vtherm.is_device_active and not vtherm.power_manager.is_overpowering_detected:
|
||||
total_power_gain += device_power
|
||||
_LOGGER.debug("vtherm %s should be in overpowering state", vtherm.name)
|
||||
await vtherm.power_manager.set_overpowering(True, device_power)
|
||||
|
||||
_LOGGER.debug("after vtherm %s total_power_gain=%s, available_power=%s", vtherm.name, total_power_gain, available_power)
|
||||
if total_power_gain >= -available_power:
|
||||
_LOGGER.debug("We have found enough vtherm to set to overpowering")
|
||||
break
|
||||
else:
|
||||
# vtherms_sorted.reverse()
|
||||
_LOGGER.debug("The available power is is > 0 (%s). Do a complete shedding/un-shedding calculation for list: %s", available_power, vtherms_sorted)
|
||||
|
||||
total_affected_power = 0
|
||||
force_overpowering = False
|
||||
|
||||
for vtherm in vtherms_sorted:
|
||||
device_power = vtherm.power_manager.device_power
|
||||
if vtherm.is_device_active:
|
||||
power_consumption_max = 0
|
||||
else:
|
||||
if vtherm.is_over_climate:
|
||||
power_consumption_max = device_power
|
||||
else:
|
||||
power_consumption_max = max(
|
||||
device_power / vtherm.nb_underlying_entities,
|
||||
device_power * vtherm.proportional_algorithm.on_percent,
|
||||
)
|
||||
|
||||
_LOGGER.debug("vtherm %s power_consumption_max is %s (device_power=%s, overclimate=%s)", vtherm.name, power_consumption_max, device_power, vtherm.is_over_climate)
|
||||
if force_overpowering or (total_affected_power + power_consumption_max >= available_power):
|
||||
_LOGGER.debug("vtherm %s should be in overpowering state", vtherm.name)
|
||||
if not vtherm.power_manager.is_overpowering_detected:
|
||||
# To force all others vtherms to be in overpowering
|
||||
force_overpowering = True
|
||||
await vtherm.power_manager.set_overpowering(True, power_consumption_max)
|
||||
else:
|
||||
total_affected_power += power_consumption_max
|
||||
# Always set to false to init the state
|
||||
_LOGGER.debug("vtherm %s should not be in overpowering state", vtherm.name)
|
||||
await vtherm.power_manager.set_overpowering(False)
|
||||
|
||||
_LOGGER.debug("after vtherm %s total_affected_power=%s, available_power=%s", vtherm.name, total_affected_power, available_power)
|
||||
|
||||
def get_climate_components_entities(self) -> list:
|
||||
"""Get all VTherms entitites"""
|
||||
@@ -256,10 +240,12 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
||||
def cmp_temps(a, b) -> int:
|
||||
diff_a = float("inf")
|
||||
diff_b = float("inf")
|
||||
if a.current_temperature is not None and a.target_temperature is not None:
|
||||
diff_a = a.target_temperature - a.current_temperature
|
||||
if b.current_temperature is not None and b.target_temperature is not None:
|
||||
diff_b = b.target_temperature - b.current_temperature
|
||||
a_target = a.target_temperature if not a.power_manager.is_overpowering_detected else a.saved_target_temp
|
||||
b_target = b.target_temperature if not b.power_manager.is_overpowering_detected else b.saved_target_temp
|
||||
if a.current_temperature is not None and a_target is not None:
|
||||
diff_a = a_target - a.current_temperature
|
||||
if b.current_temperature is not None and b_target is not None:
|
||||
diff_b = b_target - b.current_temperature
|
||||
|
||||
if diff_a == diff_b:
|
||||
return 0
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[tool.black]
|
||||
# don't work. Options are in the devcontainer.yaml
|
||||
line-length = 180
|
||||
@@ -348,7 +348,7 @@ async def test_bug_407(
|
||||
await entity.async_set_preset_mode(PRESET_COMFORT)
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
assert entity.preset_mode is PRESET_COMFORT
|
||||
assert entity.power_manager.overpowering_state is STATE_UNKNOWN
|
||||
assert entity.power_manager.overpowering_state is STATE_OFF
|
||||
assert entity.target_temperature == 18
|
||||
# waits that the heater starts
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -79,6 +79,8 @@ async def test_central_power_manager_init(
|
||||
"is_on": True,
|
||||
"current_temperature": 13,
|
||||
"target_temperature": 12,
|
||||
"saved_target_temp": 18,
|
||||
"is_overpowering_detected": False,
|
||||
},
|
||||
{
|
||||
"name": "vtherm2",
|
||||
@@ -86,6 +88,8 @@ async def test_central_power_manager_init(
|
||||
"is_on": True,
|
||||
"current_temperature": 18,
|
||||
"target_temperature": 12,
|
||||
"saved_target_temp": 18,
|
||||
"is_overpowering_detected": False,
|
||||
},
|
||||
{
|
||||
"name": "vtherm3",
|
||||
@@ -93,6 +97,8 @@ async def test_central_power_manager_init(
|
||||
"is_on": True,
|
||||
"current_temperature": 12,
|
||||
"target_temperature": 18,
|
||||
"saved_target_temp": 18,
|
||||
"is_overpowering_detected": False,
|
||||
},
|
||||
],
|
||||
["vtherm2", "vtherm1", "vtherm3"],
|
||||
@@ -106,6 +112,8 @@ async def test_central_power_manager_init(
|
||||
"is_on": True,
|
||||
"current_temperature": 13,
|
||||
"target_temperature": 12,
|
||||
"saved_target_temp": 18,
|
||||
"is_overpowering_detected": False,
|
||||
},
|
||||
{
|
||||
"name": "vtherm2",
|
||||
@@ -113,6 +121,8 @@ async def test_central_power_manager_init(
|
||||
"is_on": False,
|
||||
"current_temperature": 18,
|
||||
"target_temperature": 12,
|
||||
"saved_target_temp": 18,
|
||||
"is_overpowering_detected": False,
|
||||
},
|
||||
{
|
||||
"name": "vtherm3",
|
||||
@@ -120,6 +130,8 @@ async def test_central_power_manager_init(
|
||||
"is_on": True,
|
||||
"current_temperature": 12,
|
||||
"target_temperature": 18,
|
||||
"saved_target_temp": 18,
|
||||
"is_overpowering_detected": False,
|
||||
},
|
||||
],
|
||||
["vtherm3"],
|
||||
@@ -133,6 +145,8 @@ async def test_central_power_manager_init(
|
||||
"is_on": True,
|
||||
"current_temperature": 13,
|
||||
"target_temperature": 12,
|
||||
"saved_target_temp": 18,
|
||||
"is_overpowering_detected": False,
|
||||
},
|
||||
{
|
||||
"name": "vtherm2",
|
||||
@@ -140,6 +154,8 @@ async def test_central_power_manager_init(
|
||||
"is_on": True,
|
||||
"current_temperature": None,
|
||||
"target_temperature": 12,
|
||||
"saved_target_temp": 18,
|
||||
"is_overpowering_detected": False,
|
||||
},
|
||||
{
|
||||
"name": "vtherm3",
|
||||
@@ -147,6 +163,8 @@ async def test_central_power_manager_init(
|
||||
"is_on": True,
|
||||
"current_temperature": 12,
|
||||
"target_temperature": 18,
|
||||
"saved_target_temp": 18,
|
||||
"is_overpowering_detected": False,
|
||||
},
|
||||
],
|
||||
["vtherm1", "vtherm3", "vtherm2"],
|
||||
@@ -160,6 +178,8 @@ async def test_central_power_manager_init(
|
||||
"is_on": True,
|
||||
"current_temperature": 13,
|
||||
"target_temperature": 12,
|
||||
"saved_target_temp": 18,
|
||||
"is_overpowering_detected": False,
|
||||
},
|
||||
{
|
||||
"name": "vtherm2",
|
||||
@@ -167,6 +187,8 @@ async def test_central_power_manager_init(
|
||||
"is_on": True,
|
||||
"current_temperature": 18,
|
||||
"target_temperature": None,
|
||||
"saved_target_temp": 18,
|
||||
"is_overpowering_detected": False,
|
||||
},
|
||||
{
|
||||
"name": "vtherm3",
|
||||
@@ -174,10 +196,45 @@ async def test_central_power_manager_init(
|
||||
"is_on": True,
|
||||
"current_temperature": 12,
|
||||
"target_temperature": 18,
|
||||
"saved_target_temp": 18,
|
||||
"is_overpowering_detected": False,
|
||||
},
|
||||
],
|
||||
["vtherm1", "vtherm3", "vtherm2"],
|
||||
),
|
||||
# simple sort with overpowering detected
|
||||
(
|
||||
[
|
||||
{
|
||||
"name": "vtherm1",
|
||||
"is_configured": True,
|
||||
"is_on": True,
|
||||
"current_temperature": 13,
|
||||
# "target_temperature": 12,
|
||||
"saved_target_temp": 21,
|
||||
"is_overpowering_detected": True,
|
||||
},
|
||||
{
|
||||
"name": "vtherm2",
|
||||
"is_configured": True,
|
||||
"is_on": True,
|
||||
"current_temperature": 18,
|
||||
# "target_temperature": 12,
|
||||
"saved_target_temp": 17,
|
||||
"is_overpowering_detected": True,
|
||||
},
|
||||
{
|
||||
"name": "vtherm3",
|
||||
"is_configured": True,
|
||||
"is_on": True,
|
||||
"current_temperature": 12,
|
||||
# "target_temperature": 18,
|
||||
"saved_target_temp": 16,
|
||||
"is_overpowering_detected": True,
|
||||
},
|
||||
],
|
||||
["vtherm2", "vtherm3", "vtherm1"],
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_central_power_manageer_find_vtherms(
|
||||
@@ -194,7 +251,9 @@ async def test_central_power_manageer_find_vtherms(
|
||||
vtherm.is_on = vtherm_config.get("is_on")
|
||||
vtherm.current_temperature = vtherm_config.get("current_temperature")
|
||||
vtherm.target_temperature = vtherm_config.get("target_temperature")
|
||||
vtherm.saved_target_temp = vtherm_config.get("saved_target_temp")
|
||||
vtherm.power_manager.is_configured = vtherm_config.get("is_configured")
|
||||
vtherm.power_manager.is_overpowering_detected = vtherm_config.get("is_overpowering_detected")
|
||||
vtherms.append(vtherm)
|
||||
|
||||
with patch(
|
||||
@@ -359,6 +418,60 @@ async def test_central_power_manageer_find_vtherms(
|
||||
],
|
||||
{"vtherm1": False, "vtherm2": True, "vtherm3": True},
|
||||
),
|
||||
# Sheeding only current_power > max_power (need to gain 1000 )
|
||||
(
|
||||
2000,
|
||||
1000,
|
||||
[
|
||||
# should be overpowering
|
||||
{
|
||||
"name": "vtherm1",
|
||||
"device_power": 300,
|
||||
"is_device_active": True,
|
||||
"is_over_climate": False,
|
||||
"nb_underlying_entities": 1,
|
||||
"on_percent": 1,
|
||||
"is_overpowering_detected": False,
|
||||
},
|
||||
# should be overpowering but is already
|
||||
{
|
||||
"name": "vtherm2",
|
||||
"device_power": 600,
|
||||
"is_device_active": True,
|
||||
"is_over_climate": False,
|
||||
"nb_underlying_entities": 4,
|
||||
"on_percent": 0.1,
|
||||
"is_overpowering_detected": True,
|
||||
},
|
||||
# over_climate should be not overpowering (device not active)
|
||||
{
|
||||
"name": "vtherm3",
|
||||
"device_power": 690,
|
||||
"is_device_active": False,
|
||||
"is_over_climate": True,
|
||||
"is_overpowering_detected": False,
|
||||
},
|
||||
# over_climate should be overpowering (device active and not already overpowering)
|
||||
{
|
||||
"name": "vtherm4",
|
||||
"device_power": 690,
|
||||
"is_device_active": True,
|
||||
"is_over_climate": True,
|
||||
"is_overpowering_detected": False,
|
||||
},
|
||||
# should not overpower (keep as is)
|
||||
{
|
||||
"name": "vtherm5",
|
||||
"device_power": 800,
|
||||
"is_device_active": False,
|
||||
"is_over_climate": False,
|
||||
"nb_underlying_entities": 1,
|
||||
"on_percent": 1,
|
||||
"is_overpowering_detected": False,
|
||||
},
|
||||
],
|
||||
{"vtherm1": True, "vtherm4": True},
|
||||
),
|
||||
],
|
||||
)
|
||||
# @pytest.mark.skip
|
||||
|
||||
@@ -98,6 +98,8 @@ async def test_power_feature_manager(
|
||||
}
|
||||
)
|
||||
|
||||
power_manager.start_listening()
|
||||
|
||||
assert power_manager.is_configured is True
|
||||
assert power_manager.overpowering_state == STATE_UNKNOWN
|
||||
|
||||
@@ -197,6 +199,8 @@ async def test_power_feature_manager_set_overpowering(
|
||||
}
|
||||
)
|
||||
|
||||
power_manager.start_listening()
|
||||
|
||||
assert power_manager.is_configured is True
|
||||
assert power_manager.overpowering_state == STATE_UNKNOWN
|
||||
|
||||
|
||||
Reference in New Issue
Block a user