Compare commits
1 Commits
4.2.0.alph
...
4.2.0.alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae568c8be2 |
@@ -113,7 +113,7 @@ from .underlyings import UnderlyingEntity
|
|||||||
|
|
||||||
from .prop_algorithm import PropAlgorithm
|
from .prop_algorithm import PropAlgorithm
|
||||||
from .open_window_algorithm import WindowOpenDetectionAlgorithm
|
from .open_window_algorithm import WindowOpenDetectionAlgorithm
|
||||||
from .ema import EstimatedMobileAverage
|
from .ema import ExponentialMovingAverage
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -459,11 +459,13 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
|||||||
|
|
||||||
self._total_energy = 0
|
self._total_energy = 0
|
||||||
|
|
||||||
self._ema_algo = EstimatedMobileAverage(
|
self._ema_algo = ExponentialMovingAverage(
|
||||||
self.name,
|
self.name,
|
||||||
self._cycle_min * 60,
|
self._cycle_min * 60,
|
||||||
# Needed for time calculation
|
# Needed for time calculation
|
||||||
get_tz(self._hass),
|
get_tz(self._hass),
|
||||||
|
# one digit after the coma for temperature
|
||||||
|
1,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from datetime import datetime, tzinfo
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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
|
# 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.
|
# 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
|
# For example when using a half life of 10 minutes a measurement that is 60 minutes ago
|
||||||
@@ -17,16 +17,19 @@ MIN_TIME_DECAY_SEC = 5
|
|||||||
MAX_ALPHA = 0.9375
|
MAX_ALPHA = 0.9375
|
||||||
|
|
||||||
|
|
||||||
class EstimatedMobileAverage:
|
class ExponentialMovingAverage:
|
||||||
"""A class that will do the Estimated Mobile Average calculation"""
|
"""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"""
|
"""The halflife is the duration in secondes of a normal cycle"""
|
||||||
self._halflife: float = halflife
|
self._halflife: float = halflife
|
||||||
self._timezone = timezone
|
self._timezone = timezone
|
||||||
self._current_ema: float = None
|
self._current_ema: float = None
|
||||||
self._last_timestamp: datetime = datetime.now(self._timezone)
|
self._last_timestamp: datetime = datetime.now(self._timezone)
|
||||||
self._name = vterm_name
|
self._name = vterm_name
|
||||||
|
self._precision = precision
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"EMA-{self._name}"
|
return f"EMA-{self._name}"
|
||||||
@@ -64,8 +67,8 @@ class EstimatedMobileAverage:
|
|||||||
|
|
||||||
alpha = 1 - math.exp(math.log(0.5) * time_decay / self._halflife)
|
alpha = 1 - math.exp(math.log(0.5) * time_decay / self._halflife)
|
||||||
# capping alpha to avoid gap if last measurement was long time ago
|
# capping alpha to avoid gap if last measurement was long time ago
|
||||||
alpha = min(alpha, 0.9375)
|
alpha = min(alpha, MAX_ALPHA)
|
||||||
new_ema = round(alpha * measurement + (1 - alpha) * self._current_ema, 1)
|
new_ema = alpha * measurement + (1 - alpha) * self._current_ema
|
||||||
|
|
||||||
self._last_timestamp = timestamp
|
self._last_timestamp = timestamp
|
||||||
self._current_ema = new_ema
|
self._current_ema = new_ema
|
||||||
@@ -77,4 +80,4 @@ class EstimatedMobileAverage:
|
|||||||
self._last_timestamp,
|
self._last_timestamp,
|
||||||
)
|
)
|
||||||
|
|
||||||
return self._current_ema
|
return round(self._current_ema, self._precision)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from datetime import datetime, timedelta
|
|||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
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
|
from .commons import get_tz
|
||||||
|
|
||||||
@@ -15,12 +15,13 @@ def test_ema_basics(hass: HomeAssistant):
|
|||||||
tz = get_tz(hass) # pylint: disable=invalid-name
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
||||||
now: datetime = datetime.now(tz=tz)
|
now: datetime = datetime.now(tz=tz)
|
||||||
|
|
||||||
the_ema = EstimatedMobileAverage(
|
the_ema = ExponentialMovingAverage(
|
||||||
"test",
|
"test",
|
||||||
# 5 minutes
|
# 5 minutes
|
||||||
300,
|
300,
|
||||||
# Needed for time calculation
|
# Needed for time calculation
|
||||||
get_tz(hass),
|
get_tz(hass),
|
||||||
|
1,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert the_ema
|
assert the_ema
|
||||||
|
|||||||
Reference in New Issue
Block a user