From f1619eeb129f234c823cffd20dd5bb70a483598f Mon Sep 17 00:00:00 2001 From: Anders Lund <61545005+AnderzL7@users.noreply.github.com> Date: Mon, 8 Jan 2024 10:25:39 +0000 Subject: [PATCH 1/4] Initial commit --- custom_components/average/const.py | 5 +++-- custom_components/average/sensor.py | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/custom_components/average/const.py b/custom_components/average/const.py index af610bd..abcfec1 100644 --- a/custom_components/average/const.py +++ b/custom_components/average/const.py @@ -1,5 +1,4 @@ -""" -The Average Sensor. +"""The Average Sensor. For more details about this sensor, please refer to the documentation at https://github.com/Limych/ha-average/ @@ -50,6 +49,7 @@ ATTR_COUNT: Final = "count" ATTR_MIN_VALUE: Final = "min_value" ATTR_MAX_VALUE: Final = "max_value" +ATTR_TRENDING_TOWARDS: Final = "trending_towards" # ATTR_TO_PROPERTY: Final = [ ATTR_START, @@ -60,6 +60,7 @@ ATTR_COUNT, ATTR_MAX_VALUE, ATTR_MIN_VALUE, + ATTR_TRENDING_TOWARDS, ] diff --git a/custom_components/average/sensor.py b/custom_components/average/sensor.py index fac7c2f..c1db5e1 100644 --- a/custom_components/average/sensor.py +++ b/custom_components/average/sensor.py @@ -56,6 +56,7 @@ ATTR_MIN_VALUE, ATTR_START, ATTR_TO_PROPERTY, + ATTR_TRENDING_TOWARDS, CONF_DURATION, CONF_END, CONF_PERIOD_KEYS, @@ -142,6 +143,7 @@ class AverageSensor(SensorEntity): ATTR_COUNT, ATTR_MAX_VALUE, ATTR_MIN_VALUE, + ATTR_TRENDING_TOWARDS, } ) @@ -171,6 +173,7 @@ def __init__( self.count_sources = len(self.sources) self.available_sources = 0 self.count = 0 + self.trending_towards = None self.min_value = self.max_value = None self._attr_name = name @@ -387,6 +390,8 @@ async def _async_update_period(self): # pylint: disable=too-many-branches if start > end: start, end = end, start + self._actual_end = end + if start > now: # History hasn't been written yet for this period return @@ -443,8 +448,10 @@ async def _async_update_state( p_start, p_end = p_period # Convert times to UTC + now = dt_util.as_utc(now) start = dt_util.as_utc(start) end = dt_util.as_utc(end) + actual_end = dt_util.as_utc(self._actual_end) p_start = dt_util.as_utc(p_start) p_end = dt_util.as_utc(p_end) @@ -452,6 +459,7 @@ async def _async_update_state( now_ts = math.floor(dt_util.as_timestamp(now)) start_ts = math.floor(dt_util.as_timestamp(start)) end_ts = math.floor(dt_util.as_timestamp(end)) + actual_end_ts = math.floor(dt_util.as_timestamp(actual_end)) p_start_ts = math.floor(dt_util.as_timestamp(p_start)) p_end_ts = math.floor(dt_util.as_timestamp(p_end)) @@ -462,6 +470,7 @@ async def _async_update_state( self.available_sources = 0 values = [] + current_values = [] self.count = 0 self.min_value = self.max_value = None @@ -515,6 +524,7 @@ async def _async_update_state( last_time = start_ts if item is not None and self._has_state(item.state): last_state = self._get_state_value(item) + current_values.append(last_state) # Get the other states for item in history_list.get(entity_id): @@ -551,6 +561,19 @@ async def _async_update_state( else: self._attr_native_value = None + if current_values and values: + current_average = round( + sum(current_values) / len(current_values), self._precision + ) + if self._precision < 1: + current_average = int(current_average) + part_of_period = (now_ts - start_ts) / (actual_end_ts - start_ts) + to_now = self._attr_native_value * part_of_period + to_end = current_average * (1 - part_of_period) + self.trending_towards = to_now + to_end + + _LOGGER.debug("Current trend: %s", self.trending_towards) + _LOGGER.debug( "Total average state: %s %s", self._attr_native_value, From 057ab8ba0d3f6a5bd4c4f0efab7f1927f4002bf0 Mon Sep 17 00:00:00 2001 From: Anders Lund <61545005+AnderzL7@users.noreply.github.com> Date: Mon, 8 Jan 2024 10:58:21 +0000 Subject: [PATCH 2/4] Bugfix - use last state instead of first state --- README.md | 3 +++ custom_components/average/sensor.py | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4eebd04..e551fac 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,9 @@ I put a lot of work into making this repo and component available and updated to **max**:\ Maximum value of processed values of source sensors. +**trending_towards**:\ + The predicted value if monitored entities keep their current states for the remainder of the period. Requires "end" configuration variable to be set to actual end of period and not now(). + ## Time periods The `average` integration will execute a measure within a precise time period. You should provide none, only `duration` (when period ends at now) or exactly 2 of the following: diff --git a/custom_components/average/sensor.py b/custom_components/average/sensor.py index c1db5e1..4290803 100644 --- a/custom_components/average/sensor.py +++ b/custom_components/average/sensor.py @@ -168,6 +168,7 @@ def __init__( self._precision = precision self._undef = undef self._temperature_mode = None + self._actual_end = None self.sources = expand_entity_ids(hass, entity_ids) self.count_sources = len(self.sources) @@ -524,7 +525,6 @@ async def _async_update_state( last_time = start_ts if item is not None and self._has_state(item.state): last_state = self._get_state_value(item) - current_values.append(last_state) # Get the other states for item in history_list.get(entity_id): @@ -545,6 +545,7 @@ async def _async_update_state( last_elapsed = end_ts - last_time value += last_state * last_elapsed elapsed += last_elapsed + current_values.append(last_state) if elapsed: value /= elapsed From ad520d5abe7295b06d26eee46231d6bd26b4164c Mon Sep 17 00:00:00 2001 From: Anders Lund Date: Thu, 7 Mar 2024 09:56:21 +0100 Subject: [PATCH 3/4] Calculates trend attribute without storing values in seperate array --- custom_components/average/sensor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/custom_components/average/sensor.py b/custom_components/average/sensor.py index 4290803..2e01739 100644 --- a/custom_components/average/sensor.py +++ b/custom_components/average/sensor.py @@ -471,9 +471,9 @@ async def _async_update_state( self.available_sources = 0 values = [] - current_values = [] self.count = 0 self.min_value = self.max_value = None + trending_last_state = 0 # pylint: disable=too-many-nested-blocks for entity_id in self.sources: @@ -545,7 +545,7 @@ async def _async_update_state( last_elapsed = end_ts - last_time value += last_state * last_elapsed elapsed += last_elapsed - current_values.append(last_state) + trending_last_state = last_state if elapsed: value /= elapsed @@ -562,9 +562,9 @@ async def _async_update_state( else: self._attr_native_value = None - if current_values and values: + if trending_last_state: current_average = round( - sum(current_values) / len(current_values), self._precision + (sum(values) + trending_last_state) / (len(values) + 1), self._precision ) if self._precision < 1: current_average = int(current_average) From 09bb2200447730e34a522b12719660182252c008 Mon Sep 17 00:00:00 2001 From: Anders Lund Date: Thu, 7 Mar 2024 10:21:51 +0100 Subject: [PATCH 4/4] Bugfix: Trending_towards support multiple sources --- custom_components/average/sensor.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/custom_components/average/sensor.py b/custom_components/average/sensor.py index 2e01739..be270e7 100644 --- a/custom_components/average/sensor.py +++ b/custom_components/average/sensor.py @@ -473,7 +473,8 @@ async def _async_update_state( values = [] self.count = 0 self.min_value = self.max_value = None - trending_last_state = 0 + last_values = [] + # pylint: disable=too-many-nested-blocks for entity_id in self.sources: @@ -489,6 +490,8 @@ async def _async_update_state( value = 0 elapsed = 0 + trending_last_state = None + if self._period is None: # Get current state @@ -554,6 +557,9 @@ async def _async_update_state( if isinstance(value, numbers.Number): values.append(value) self.available_sources += 1 + + if isinstance(trending_last_state, numbers.Number): + last_values.append(trending_last_state) if values: self._attr_native_value = round(sum(values) / len(values), self._precision) @@ -562,9 +568,9 @@ async def _async_update_state( else: self._attr_native_value = None - if trending_last_state: + if last_values: current_average = round( - (sum(values) + trending_last_state) / (len(values) + 1), self._precision + sum(last_values) / len(last_values), self._precision ) if self._precision < 1: current_average = int(current_average) @@ -572,6 +578,8 @@ async def _async_update_state( to_now = self._attr_native_value * part_of_period to_end = current_average * (1 - part_of_period) self.trending_towards = to_now + to_end + else: + self.trending_towards = None _LOGGER.debug("Current trend: %s", self.trending_towards)