Compare commits
4 Commits
4.2.0.alph
...
4.2.0.alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae568c8be2 | ||
|
|
8fe4eb7ac0 | ||
|
|
328f5f7cb0 | ||
|
|
41ae572875 |
@@ -109,17 +109,21 @@ from .const import (
|
||||
PRESET_AC_SUFFIX,
|
||||
)
|
||||
|
||||
from .commons import get_tz
|
||||
|
||||
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__)
|
||||
|
||||
|
||||
def get_tz(hass: HomeAssistant):
|
||||
"""Get the current timezone"""
|
||||
|
||||
return dt_util.get_time_zone(hass.config.time_zone)
|
||||
|
||||
|
||||
class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
"""Representation of a base class for all Versatile Thermostat device."""
|
||||
|
||||
@@ -249,7 +253,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
|
||||
self._underlyings = []
|
||||
|
||||
self._smooth_temp = None
|
||||
self._ema_temp = None
|
||||
self._ema_algo = None
|
||||
self.post_init(entry_infos)
|
||||
|
||||
@@ -455,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),
|
||||
# one digit after the coma for temperature
|
||||
1,
|
||||
)
|
||||
|
||||
_LOGGER.debug(
|
||||
|
||||
@@ -8,7 +8,7 @@ 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
|
||||
@@ -17,16 +17,19 @@ MIN_TIME_DECAY_SEC = 5
|
||||
MAX_ALPHA = 0.9375
|
||||
|
||||
|
||||
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,8 +67,8 @@ 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
|
||||
@@ -77,4 +80,4 @@ class EstimatedMobileAverage:
|
||||
self._last_timestamp,
|
||||
)
|
||||
|
||||
return self._current_ema
|
||||
return round(self._current_ema, self._precision)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# pylint: disable=line-too-long
|
||||
""" This file implements the Open Window by temperature algorithm
|
||||
This algo works the following way:
|
||||
- each time a new temperature is measured
|
||||
@@ -12,7 +13,7 @@ from datetime import datetime
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# To filter bad values
|
||||
MIN_DELTA_T_SEC = 30 # two temp mesure should be > 10 sec
|
||||
MIN_DELTA_T_SEC = 15 # two temp mesure should be > 10 sec
|
||||
MAX_SLOPE_VALUE = 2 # slope cannot be > 2 or < -2 -> else this is an aberrant point
|
||||
|
||||
|
||||
@@ -71,10 +72,10 @@ class WindowOpenDetectionAlgorithm:
|
||||
)
|
||||
return lspe
|
||||
|
||||
if self._last_slope is None:
|
||||
self._last_slope = new_slope
|
||||
else:
|
||||
self._last_slope = (0.5 * self._last_slope) + (0.5 * new_slope)
|
||||
# if self._last_slope is None:
|
||||
self._last_slope = round(new_slope, 4)
|
||||
# else:
|
||||
# self._last_slope = (0.5 * self._last_slope) + (0.5 * new_slope)
|
||||
|
||||
self._last_datetime = datetime_measure
|
||||
self._last_temperature = temperature
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -386,6 +386,7 @@ async def test_multiple_climates(
|
||||
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE,
|
||||
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
||||
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
||||
CONF_CYCLE_MIN: 8,
|
||||
CONF_TEMP_MIN: 15,
|
||||
CONF_TEMP_MAX: 30,
|
||||
"eco_temp": 17,
|
||||
@@ -486,6 +487,7 @@ async def test_multiple_climates_underlying_changes(
|
||||
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE,
|
||||
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
||||
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
||||
CONF_CYCLE_MIN: 8,
|
||||
CONF_TEMP_MIN: 15,
|
||||
CONF_TEMP_MAX: 30,
|
||||
"eco_temp": 17,
|
||||
|
||||
Reference in New Issue
Block a user