Compare commits

...

2 Commits

Author SHA1 Message Date
Jean-Marc Collin 9e15aa48b9 Maia comments: change MAX_ALPHA to 0.5, add slope calculation at each cycle. 2023-11-21 18:56:26 +00:00
Jean-Marc Collin ae568c8be2 Take Maia feedbacks on the algo. 2023-11-18 23:33:40 +00:00
3 changed files with 24 additions and 13 deletions
@@ -113,7 +113,7 @@ from .underlyings import UnderlyingEntity
from .prop_algorithm import PropAlgorithm
from .open_window_algorithm import WindowOpenDetectionAlgorithm
from .ema import EstimatedMobileAverage
from .ema import ExponentialMovingAverage
_LOGGER = logging.getLogger(__name__)
@@ -459,11 +459,13 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
self._total_energy = 0
self._ema_algo = EstimatedMobileAverage(
self._ema_algo = ExponentialMovingAverage(
self.name,
self._cycle_min * 60,
# Needed for time calculation
get_tz(self._hass),
# two digits after the coma for temperature slope calculation
2,
)
_LOGGER.debug(
@@ -2093,6 +2095,9 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
force,
)
# calculate the smooth_temperature with EMA calculation
await self._async_manage_window_auto()
self.update_custom_attributes()
return True
+14 -9
View File
@@ -8,25 +8,28 @@ from datetime import datetime, tzinfo
_LOGGER = logging.getLogger(__name__)
MIN_TIME_DECAY_SEC = 5
MIN_TIME_DECAY_SEC = 0
# As for the EMA calculation of irregular time series, I've seen that it might be useful to
# have an upper limit for alpha in case the last measurement was too long ago.
# For example when using a half life of 10 minutes a measurement that is 60 minutes ago
# (if there's nothing inbetween) would contribute to the smoothed value with 1,5%,
# giving the current measurement 98,5% relevance. It could be wise to limit the alpha to e.g. 4x the half life (=0.9375).
MAX_ALPHA = 0.9375
MAX_ALPHA = 0.5
class EstimatedMobileAverage:
class ExponentialMovingAverage:
"""A class that will do the Estimated Mobile Average calculation"""
def __init__(self, vterm_name: str, halflife: float, timezone: tzinfo):
def __init__(
self, vterm_name: str, halflife: float, timezone: tzinfo, precision: int = 3
):
"""The halflife is the duration in secondes of a normal cycle"""
self._halflife: float = halflife
self._timezone = timezone
self._current_ema: float = None
self._last_timestamp: datetime = datetime.now(self._timezone)
self._name = vterm_name
self._precision = precision
def __str__(self) -> str:
return f"EMA-{self._name}"
@@ -64,17 +67,19 @@ class EstimatedMobileAverage:
alpha = 1 - math.exp(math.log(0.5) * time_decay / self._halflife)
# capping alpha to avoid gap if last measurement was long time ago
alpha = min(alpha, 0.9375)
new_ema = round(alpha * measurement + (1 - alpha) * self._current_ema, 1)
alpha = min(alpha, MAX_ALPHA)
new_ema = alpha * measurement + (1 - alpha) * self._current_ema
self._last_timestamp = timestamp
self._current_ema = new_ema
_LOGGER.debug(
"%s - alpha=%.2f new_ema=%.2f last_timestamp=%s",
"%s - timestamp=%s alpha=%.2f measurement=%.2f current_ema=%.2f new_ema=%.2f",
self,
timestamp,
alpha,
measurement,
self._current_ema,
self._last_timestamp,
new_ema,
)
return self._current_ema
return round(self._current_ema, self._precision)
+3 -2
View File
@@ -4,7 +4,7 @@ from datetime import datetime, timedelta
from homeassistant.core import HomeAssistant
from custom_components.versatile_thermostat.ema import EstimatedMobileAverage
from custom_components.versatile_thermostat.ema import ExponentialMovingAverage
from .commons import get_tz
@@ -15,12 +15,13 @@ def test_ema_basics(hass: HomeAssistant):
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
the_ema = EstimatedMobileAverage(
the_ema = ExponentialMovingAverage(
"test",
# 5 minutes
300,
# Needed for time calculation
get_tz(hass),
1,
)
assert the_ema