From 1ed2c08f343500602a94d33a3d20528197c402ea Mon Sep 17 00:00:00 2001 From: Julian Pawlowski Date: Fri, 7 Nov 2025 15:16:16 +0000 Subject: [PATCH] feat: Add minimum period length configuration for best and peak price sensors --- .../tibber_prices/binary_sensor.py | 37 ++++++++++++++++++ .../tibber_prices/config_flow.py | 38 +++++++++++++++++++ custom_components/tibber_prices/const.py | 4 ++ .../tibber_prices/translations/de.json | 2 + .../tibber_prices/translations/en.json | 2 + .../tibber_prices/translations/nb.json | 2 + .../tibber_prices/translations/nl.json | 2 + .../tibber_prices/translations/sv.json | 2 + 8 files changed, 89 insertions(+) diff --git a/custom_components/tibber_prices/binary_sensor.py b/custom_components/tibber_prices/binary_sensor.py index b3a3d45..9891c04 100644 --- a/custom_components/tibber_prices/binary_sensor.py +++ b/custom_components/tibber_prices/binary_sensor.py @@ -29,14 +29,18 @@ if TYPE_CHECKING: from .const import ( CONF_BEST_PRICE_FLEX, CONF_BEST_PRICE_MIN_DISTANCE_FROM_AVG, + CONF_BEST_PRICE_MIN_PERIOD_LENGTH, CONF_EXTENDED_DESCRIPTIONS, CONF_PEAK_PRICE_FLEX, CONF_PEAK_PRICE_MIN_DISTANCE_FROM_AVG, + CONF_PEAK_PRICE_MIN_PERIOD_LENGTH, DEFAULT_BEST_PRICE_FLEX, DEFAULT_BEST_PRICE_MIN_DISTANCE_FROM_AVG, + DEFAULT_BEST_PRICE_MIN_PERIOD_LENGTH, DEFAULT_EXTENDED_DESCRIPTIONS, DEFAULT_PEAK_PRICE_FLEX, DEFAULT_PEAK_PRICE_MIN_DISTANCE_FROM_AVG, + DEFAULT_PEAK_PRICE_MIN_PERIOD_LENGTH, async_get_entity_description, get_entity_description, ) @@ -501,6 +505,37 @@ class TibberPricesBinarySensor(TibberPricesEntity, BinarySensorEntity): periods.append(current_period) return periods + def _filter_periods_by_min_length(self, periods: list[list[dict]], *, reverse_sort: bool) -> list[list[dict]]: + """ + Filter periods to only include those meeting the minimum length requirement. + + Args: + periods: List of periods (each period is a list of interval dicts) + reverse_sort: True for peak price, False for best price + + Returns: + Filtered list of periods that meet minimum length requirement + + """ + options = self.coordinator.config_entry.options + data = self.coordinator.config_entry.data + + # Use appropriate config based on sensor type + if reverse_sort: # Peak price + conf_key = CONF_PEAK_PRICE_MIN_PERIOD_LENGTH + default = DEFAULT_PEAK_PRICE_MIN_PERIOD_LENGTH + else: # Best price + conf_key = CONF_BEST_PRICE_MIN_PERIOD_LENGTH + default = DEFAULT_BEST_PRICE_MIN_PERIOD_LENGTH + + min_period_length = options.get(conf_key, data.get(conf_key, default)) + + # Convert minutes to number of 15-minute intervals + min_intervals = min_period_length // MINUTES_PER_INTERVAL + + # Filter out periods that are too short + return [period for period in periods if len(period) >= min_intervals] + def _add_interval_ends(self, periods: list[list[dict]]) -> None: """Add interval_end to each interval using per-interval interval_length.""" for period in periods: @@ -580,6 +615,8 @@ class TibberPricesBinarySensor(TibberPricesEntity, BinarySensorEntity): price_context, reverse_sort=reverse_sort, ) + # Filter periods by minimum length requirement + periods = self._filter_periods_by_min_length(periods, reverse_sort=reverse_sort) self._add_interval_ends(periods) # Only use periods relevant for today/tomorrow for annotation and attribute calculation filtered_periods = self._filter_periods_today_tomorrow(periods) diff --git a/custom_components/tibber_prices/config_flow.py b/custom_components/tibber_prices/config_flow.py index 34bcdbb..b794fc5 100644 --- a/custom_components/tibber_prices/config_flow.py +++ b/custom_components/tibber_prices/config_flow.py @@ -40,16 +40,20 @@ from .api import ( from .const import ( CONF_BEST_PRICE_FLEX, CONF_BEST_PRICE_MIN_DISTANCE_FROM_AVG, + CONF_BEST_PRICE_MIN_PERIOD_LENGTH, CONF_EXTENDED_DESCRIPTIONS, CONF_PEAK_PRICE_FLEX, CONF_PEAK_PRICE_MIN_DISTANCE_FROM_AVG, + CONF_PEAK_PRICE_MIN_PERIOD_LENGTH, CONF_PRICE_RATING_THRESHOLD_HIGH, CONF_PRICE_RATING_THRESHOLD_LOW, DEFAULT_BEST_PRICE_FLEX, DEFAULT_BEST_PRICE_MIN_DISTANCE_FROM_AVG, + DEFAULT_BEST_PRICE_MIN_PERIOD_LENGTH, DEFAULT_EXTENDED_DESCRIPTIONS, DEFAULT_PEAK_PRICE_FLEX, DEFAULT_PEAK_PRICE_MIN_DISTANCE_FROM_AVG, + DEFAULT_PEAK_PRICE_MIN_PERIOD_LENGTH, DEFAULT_PRICE_RATING_THRESHOLD_HIGH, DEFAULT_PRICE_RATING_THRESHOLD_LOW, DOMAIN, @@ -526,6 +530,23 @@ class TibberPricesOptionsFlowHandler(OptionsFlow): step_id="best_price", data_schema=vol.Schema( { + vol.Optional( + CONF_BEST_PRICE_MIN_PERIOD_LENGTH, + default=int( + self.config_entry.options.get( + CONF_BEST_PRICE_MIN_PERIOD_LENGTH, + DEFAULT_BEST_PRICE_MIN_PERIOD_LENGTH, + ) + ), + ): NumberSelector( + NumberSelectorConfig( + min=15, + max=240, + step=15, + unit_of_measurement="min", + mode=NumberSelectorMode.SLIDER, + ), + ), vol.Optional( CONF_BEST_PRICE_FLEX, default=int( @@ -572,6 +593,23 @@ class TibberPricesOptionsFlowHandler(OptionsFlow): step_id="peak_price", data_schema=vol.Schema( { + vol.Optional( + CONF_PEAK_PRICE_MIN_PERIOD_LENGTH, + default=int( + self.config_entry.options.get( + CONF_PEAK_PRICE_MIN_PERIOD_LENGTH, + DEFAULT_PEAK_PRICE_MIN_PERIOD_LENGTH, + ) + ), + ): NumberSelector( + NumberSelectorConfig( + min=15, + max=240, + step=15, + unit_of_measurement="min", + mode=NumberSelectorMode.SLIDER, + ), + ), vol.Optional( CONF_PEAK_PRICE_FLEX, default=int( diff --git a/custom_components/tibber_prices/const.py b/custom_components/tibber_prices/const.py index 0928f1e..00a3551 100644 --- a/custom_components/tibber_prices/const.py +++ b/custom_components/tibber_prices/const.py @@ -25,6 +25,8 @@ CONF_BEST_PRICE_FLEX = "best_price_flex" CONF_PEAK_PRICE_FLEX = "peak_price_flex" CONF_BEST_PRICE_MIN_DISTANCE_FROM_AVG = "best_price_min_distance_from_avg" CONF_PEAK_PRICE_MIN_DISTANCE_FROM_AVG = "peak_price_min_distance_from_avg" +CONF_BEST_PRICE_MIN_PERIOD_LENGTH = "best_price_min_period_length" +CONF_PEAK_PRICE_MIN_PERIOD_LENGTH = "peak_price_min_period_length" CONF_PRICE_RATING_THRESHOLD_LOW = "price_rating_threshold_low" CONF_PRICE_RATING_THRESHOLD_HIGH = "price_rating_threshold_high" @@ -37,6 +39,8 @@ DEFAULT_BEST_PRICE_FLEX = 15 # 15% flexibility for best price (user-facing, per DEFAULT_PEAK_PRICE_FLEX = -15 # 15% flexibility for peak price (user-facing, percent) DEFAULT_BEST_PRICE_MIN_DISTANCE_FROM_AVG = 2 # 2% minimum distance from daily average for best price DEFAULT_PEAK_PRICE_MIN_DISTANCE_FROM_AVG = 2 # 2% minimum distance from daily average for peak price +DEFAULT_BEST_PRICE_MIN_PERIOD_LENGTH = 60 # 60 minutes minimum period length for best price (user-facing, minutes) +DEFAULT_PEAK_PRICE_MIN_PERIOD_LENGTH = 60 # 60 minutes minimum period length for peak price (user-facing, minutes) DEFAULT_PRICE_RATING_THRESHOLD_LOW = -10 # Default rating threshold low percentage DEFAULT_PRICE_RATING_THRESHOLD_HIGH = 10 # Default rating threshold high percentage diff --git a/custom_components/tibber_prices/translations/de.json b/custom_components/tibber_prices/translations/de.json index b2d5914..5944ee7 100644 --- a/custom_components/tibber_prices/translations/de.json +++ b/custom_components/tibber_prices/translations/de.json @@ -96,6 +96,7 @@ "title": "Bestpreis-Periode Einstellungen", "description": "Konfiguration für den Bestpreis-Periode Binärsensor. Dieser Sensor ist während der Zeiträume mit den niedrigsten Strompreisen aktiv.", "data": { + "best_price_min_period_length": "Minimale Periodenlänge", "best_price_flex": "Flexibilität: Maximale % über dem Mindestpreis", "best_price_min_distance_from_avg": "Mindestabstand: Erforderliche % unter dem Tagesdurchschnitt" } @@ -104,6 +105,7 @@ "title": "Spitzenpreis-Periode Einstellungen", "description": "Konfiguration für den Spitzenpreis-Periode Binärsensor. Dieser Sensor ist während der Zeiträume mit den höchsten Strompreisen aktiv.", "data": { + "peak_price_min_period_length": "Minimale Periodenlänge", "peak_price_flex": "Flexibilität: Maximale % unter dem Höchstpreis (negativer Wert)", "peak_price_min_distance_from_avg": "Mindestabstand: Erforderliche % über dem Tagesdurchschnitt" } diff --git a/custom_components/tibber_prices/translations/en.json b/custom_components/tibber_prices/translations/en.json index ed9c08d..f2793f0 100644 --- a/custom_components/tibber_prices/translations/en.json +++ b/custom_components/tibber_prices/translations/en.json @@ -96,6 +96,7 @@ "title": "Best Price Period Settings", "description": "Configure settings for the Best Price Period binary sensor. This sensor is active during periods with the lowest electricity prices.", "data": { + "best_price_min_period_length": "Minimum Period Length", "best_price_flex": "Flexibility: Maximum % above minimum price", "best_price_min_distance_from_avg": "Minimum Distance: Required % below daily average" } @@ -104,6 +105,7 @@ "title": "Peak Price Period Settings", "description": "Configure settings for the Peak Price Period binary sensor. This sensor is active during periods with the highest electricity prices.", "data": { + "peak_price_min_period_length": "Minimum Period Length", "peak_price_flex": "Flexibility: Maximum % below maximum price (negative value)", "peak_price_min_distance_from_avg": "Minimum Distance: Required % above daily average" } diff --git a/custom_components/tibber_prices/translations/nb.json b/custom_components/tibber_prices/translations/nb.json index e8584c5..e562138 100644 --- a/custom_components/tibber_prices/translations/nb.json +++ b/custom_components/tibber_prices/translations/nb.json @@ -96,6 +96,7 @@ "title": "Innstillinger for beste prisperiode", "description": "Konfigurer innstillinger for binærsensoren Beste prisperiode. Denne sensoren er aktiv i perioder med de laveste strømprisene.", "data": { + "best_price_min_period_length": "Minimum periodelengde", "best_price_flex": "Fleksibilitet: Maksimum % over minimumspris", "best_price_min_distance_from_avg": "Minimumsavstand: Påkrevd % under daglig gjennomsnitt" } @@ -104,6 +105,7 @@ "title": "Innstillinger for topprisperiode", "description": "Konfigurer innstillinger for binærsensoren Topprisperiode. Denne sensoren er aktiv i perioder med de høyeste strømprisene.", "data": { + "peak_price_min_period_length": "Minimum periodelengde", "peak_price_flex": "Fleksibilitet: Maksimum % under maksimumspris (negativ verdi)", "peak_price_min_distance_from_avg": "Minimumsavstand: Påkrevd % over daglig gjennomsnitt" } diff --git a/custom_components/tibber_prices/translations/nl.json b/custom_components/tibber_prices/translations/nl.json index 962703d..03cda2a 100644 --- a/custom_components/tibber_prices/translations/nl.json +++ b/custom_components/tibber_prices/translations/nl.json @@ -96,6 +96,7 @@ "title": "Instellingen beste prijsperiode", "description": "Configureer instellingen voor de Beste Prijsperiode binaire sensor. Deze sensor is actief tijdens perioden met de laagste elektriciteitsprijzen.", "data": { + "best_price_min_period_length": "Minimale periode lengte", "best_price_flex": "Flexibiliteit: Maximaal % boven minimumprijs", "best_price_min_distance_from_avg": "Minimale afstand: Vereist % onder dagelijks gemiddelde" } @@ -104,6 +105,7 @@ "title": "Instellingen piekprijsperiode", "description": "Configureer instellingen voor de Piekprijsperiode binaire sensor. Deze sensor is actief tijdens perioden met de hoogste elektriciteitsprijzen.", "data": { + "peak_price_min_period_length": "Minimale periode lengte", "peak_price_flex": "Flexibiliteit: Maximaal % onder maximumprijs (negatieve waarde)", "peak_price_min_distance_from_avg": "Minimale afstand: Vereist % boven dagelijks gemiddelde" } diff --git a/custom_components/tibber_prices/translations/sv.json b/custom_components/tibber_prices/translations/sv.json index 20a8c00..66a8df3 100644 --- a/custom_components/tibber_prices/translations/sv.json +++ b/custom_components/tibber_prices/translations/sv.json @@ -96,6 +96,7 @@ "title": "Inställningar för bästa prisperiod", "description": "Konfigurera inställningar för Bästa Prisperiod binärsensor. Denna sensor är aktiv under perioder med de lägsta elpriserna.", "data": { + "best_price_min_period_length": "Minsta periodlängd", "best_price_flex": "Flexibilitet: Maximalt % över minimumpris", "best_price_min_distance_from_avg": "Minimiavstånd: Krävd % under dagligt genomsnitt" } @@ -104,6 +105,7 @@ "title": "Inställningar för topprisperiod", "description": "Konfigurera inställningar för Topprisperiod binärsensor. Denna sensor är aktiv under perioder med de högsta elpriserna.", "data": { + "peak_price_min_period_length": "Minsta periodlängd", "peak_price_flex": "Flexibilitet: Maximalt % under maximumpris (negativt värde)", "peak_price_min_distance_from_avg": "Minimiavstånd: Krävd % över dagligt genomsnitt" }