From 52cfc4a87f4f247c0be4c1b5f837b2076b2f0ef3 Mon Sep 17 00:00:00 2001 From: Julian Pawlowski Date: Sat, 17 May 2025 17:39:06 +0000 Subject: [PATCH] refactoring --- .../tibber_prices/binary_sensor.py | 300 +++++++++++++----- .../tibber_prices/config_flow.py | 61 +++- custom_components/tibber_prices/const.py | 4 + .../tibber_prices/custom_translations/de.json | 22 +- .../tibber_prices/custom_translations/en.json | 22 +- custom_components/tibber_prices/sensor.py | 4 +- .../tibber_prices/translations/de.json | 18 +- .../tibber_prices/translations/en.json | 26 +- 8 files changed, 329 insertions(+), 128 deletions(-) diff --git a/custom_components/tibber_prices/binary_sensor.py b/custom_components/tibber_prices/binary_sensor.py index e2688e8..d6ac79a 100644 --- a/custom_components/tibber_prices/binary_sensor.py +++ b/custom_components/tibber_prices/binary_sensor.py @@ -2,7 +2,7 @@ from __future__ import annotations -from datetime import datetime +from datetime import datetime, timedelta from typing import TYPE_CHECKING from homeassistant.components.binary_sensor import ( @@ -10,7 +10,7 @@ from homeassistant.components.binary_sensor import ( BinarySensorEntity, BinarySensorEntityDescription, ) -from homeassistant.const import EntityCategory +from homeassistant.const import PERCENTAGE, EntityCategory from homeassistant.util import dt as dt_util from .entity import TibberPricesEntity @@ -25,16 +25,23 @@ if TYPE_CHECKING: from .coordinator import TibberPricesDataUpdateCoordinator from .data import TibberPricesConfigEntry +from .const import ( + CONF_BEST_PRICE_FLEX, + CONF_PEAK_PRICE_FLEX, + DEFAULT_BEST_PRICE_FLEX, + DEFAULT_PEAK_PRICE_FLEX, +) + ENTITY_DESCRIPTIONS = ( BinarySensorEntityDescription( - key="peak_interval", - translation_key="peak_interval", + key="peak_price_period", + translation_key="peak_price_period", name="Peak Price Interval", icon="mdi:clock-alert", ), BinarySensorEntityDescription( - key="best_price_interval", - translation_key="best_price_interval", + key="best_price_period", + translation_key="best_price_period", name="Best Price Interval", icon="mdi:clock-check", ), @@ -82,25 +89,70 @@ class TibberPricesBinarySensor(TibberPricesEntity, BinarySensorEntity): """Return the appropriate state getter method based on the sensor type.""" key = self.entity_description.key - if key == "peak_interval": - return lambda: self._get_price_threshold_state(threshold_percentage=0.8, high_is_active=True) - if key == "best_price_interval": - return lambda: self._get_price_threshold_state(threshold_percentage=0.2, high_is_active=False) + if key == "peak_price_period": + return self._peak_price_state + if key == "best_price_period": + return self._best_price_state if key == "connection": return lambda: True if self.coordinator.data else None return None + def _get_flex_option(self, option_key: str, default: float) -> float: + """Get a float option from config entry options or fallback to default. Accepts 0-100.""" + options = self.coordinator.config_entry.options + data = self.coordinator.config_entry.data + value = options.get(option_key, data.get(option_key, default)) + try: + value = float(value) / 100 + except (TypeError, ValueError): + value = default + return value + + def _best_price_state(self) -> bool | None: + """Return True if current price is within +flex% of the day's minimum price.""" + price_data = self._get_current_price_data() + if not price_data: + return None + prices, current_price = price_data + min_price = min(prices) + flex = self._get_flex_option(CONF_BEST_PRICE_FLEX, DEFAULT_BEST_PRICE_FLEX) + threshold = min_price * (1 + flex) + return current_price <= threshold + + def _peak_price_state(self) -> bool | None: + """Return True if current price is within -flex% of the day's maximum price.""" + price_data = self._get_current_price_data() + if not price_data: + return None + prices, current_price = price_data + max_price = max(prices) + flex = self._get_flex_option(CONF_PEAK_PRICE_FLEX, DEFAULT_PEAK_PRICE_FLEX) + threshold = max_price * (1 - flex) + return current_price >= threshold + + def _get_price_threshold_state(self, *, threshold_percentage: float, high_is_active: bool) -> bool | None: + """Deprecate: use _best_price_state or _peak_price_state for those sensors.""" + price_data = self._get_current_price_data() + if not price_data: + return None + + prices, current_price = price_data + threshold_index = int(len(prices) * threshold_percentage) + + if high_is_active: + return current_price >= prices[threshold_index] + + return current_price <= prices[threshold_index] + def _get_attribute_getter(self) -> Callable | None: """Return the appropriate attribute getter method based on the sensor type.""" key = self.entity_description.key - if key == "peak_interval": - return lambda: self._get_price_intervals_attributes(attribute_name="peak_intervals", reverse_sort=True) - if key == "best_price_interval": - return lambda: self._get_price_intervals_attributes( - attribute_name="best_price_intervals", reverse_sort=False - ) + if key == "peak_price_period": + return lambda: self._get_price_intervals_attributes(reverse_sort=True) + if key == "best_price_period": + return lambda: self._get_price_intervals_attributes(reverse_sort=False) return None @@ -130,33 +182,93 @@ class TibberPricesBinarySensor(TibberPricesEntity, BinarySensorEntity): prices.sort() return prices, float(current_interval_data["total"]) - def _get_price_threshold_state(self, *, threshold_percentage: float, high_is_active: bool) -> bool | None: + def _annotate_period_intervals( + self, + periods: list[list[dict]], + ref_price: float, + avg_price: float, + interval_minutes: int, + ) -> list[dict]: + """Return flattened and annotated intervals with period info and requested properties.""" + # Determine reference type for naming + reference_type = None + if self.entity_description.key == "best_price_period": + reference_type = "min" + elif self.entity_description.key == "peak_price_period": + reference_type = "max" + else: + reference_type = "ref" + # Set attribute name suffixes + if reference_type == "min": + diff_key = "price_diff_from_min" + diff_ct_key = "price_diff_from_min_ct" + diff_pct_key = "price_diff_from_min_" + PERCENTAGE + elif reference_type == "max": + diff_key = "price_diff_from_max" + diff_ct_key = "price_diff_from_max_ct" + diff_pct_key = "price_diff_from_max_" + PERCENTAGE + else: + diff_key = "price_diff" + diff_ct_key = "price_diff_ct" + diff_pct_key = "price_diff_" + PERCENTAGE + result = [] + period_count = len(periods) + for idx, period in enumerate(periods, 1): + period_start = period[0]["interval_start"] if period else None + period_start_hour = period_start.hour if period_start else None + period_start_minute = period_start.minute if period_start else None + period_start_time = f"{period_start_hour:02d}:{period_start_minute:02d}" if period_start else None + period_end = period[-1]["interval_end"] if period else None + interval_count = len(period) + period_length = interval_count * interval_minutes + periods_remaining = len(periods) - idx + for interval_idx, interval in enumerate(period, 1): + interval_copy = interval.copy() + interval_remaining = interval_count - interval_idx + # Compose new dict with period-related keys first, then interval timing, then price info + new_interval = { + "period_start": period_start, + "period_end": period_end, + "hour": period_start_hour, + "minute": period_start_minute, + "time": period_start_time, + "period_length_minute": period_length, + "period_remaining_minute_after_interval": interval_remaining * interval_minutes, + "periods_total": period_count, + "periods_remaining": periods_remaining, + "interval_total": interval_count, + "interval_remaining": interval_remaining, + "interval_position": interval_idx, + } + # Add interval timing + new_interval["interval_start"] = interval_copy.pop("interval_start", None) + new_interval["interval_end"] = interval_copy.pop("interval_end", None) + # Add hour, minute, time, price if present in interval_copy + for k in ("interval_hour", "interval_minute", "interval_time", "price"): + if k in interval_copy: + new_interval[k] = interval_copy.pop(k) + # Add the rest of the interval info (e.g. price_ct, price_difference_*, etc.) + new_interval.update(interval_copy) + new_interval["price_ct"] = round(new_interval["price"] * 100, 2) + price_diff = new_interval["price"] - ref_price + new_interval[diff_key] = round(price_diff, 4) + new_interval[diff_ct_key] = round(price_diff * 100, 2) + price_diff_percent = ((new_interval["price"] - ref_price) / ref_price) * 100 if ref_price != 0 else 0.0 + new_interval[diff_pct_key] = round(price_diff_percent, 2) + # Add difference to average price of the day (avg_price is now passed in) + avg_diff = new_interval["price"] - avg_price + new_interval["price_diff_from_avg"] = round(avg_diff, 4) + new_interval["price_diff_from_avg_ct"] = round(avg_diff * 100, 2) + avg_diff_percent = ((new_interval["price"] - avg_price) / avg_price) * 100 if avg_price != 0 else 0.0 + new_interval["price_diff_from_avg_" + PERCENTAGE] = round(avg_diff_percent, 2) + result.append(new_interval) + return result + + def _get_price_intervals_attributes(self, *, reverse_sort: bool) -> dict | None: """ - Determine if current price is above/below threshold. + Get price interval attributes with support for 15-minute intervals and period grouping. Args: - threshold_percentage: The percentage point in the sorted list (0.0-1.0) - high_is_active: If True, value >= threshold is active, otherwise value <= threshold is active - - """ - price_data = self._get_current_price_data() - if not price_data: - return None - - prices, current_price = price_data - threshold_index = int(len(prices) * threshold_percentage) - - if high_is_active: - return current_price >= prices[threshold_index] - - return current_price <= prices[threshold_index] - - def _get_price_intervals_attributes(self, *, attribute_name: str, reverse_sort: bool) -> dict | None: - """ - Get price interval attributes with support for 15-minute intervals. - - Args: - attribute_name: The attribute name to use in the result dictionary reverse_sort: Whether to sort prices in reverse (high to low) Returns: @@ -172,51 +284,91 @@ class TibberPricesBinarySensor(TibberPricesEntity, BinarySensorEntity): if not today_prices: return None - # Detect the granularity of the data interval_minutes = detect_interval_granularity(today_prices) - # Build a list of price data with timestamps and values - price_intervals = [] + # Use entity type to determine flex and logic, but always use 'price_intervals' as attribute name + if reverse_sort is False: # best_price_period entity + flex = self._get_flex_option(CONF_BEST_PRICE_FLEX, DEFAULT_BEST_PRICE_FLEX) + prices = [float(p["total"]) for p in today_prices] + min_price = min(prices) + + def in_range(price: float) -> bool: + return price <= min_price * (1 + flex) + + ref_price = min_price + elif reverse_sort is True: # peak_price_period entity + flex = self._get_flex_option(CONF_PEAK_PRICE_FLEX, DEFAULT_PEAK_PRICE_FLEX) + prices = [float(p["total"]) for p in today_prices] + max_price = max(prices) + + def in_range(price: float) -> bool: + return price >= max_price * (1 - flex) + + ref_price = max_price + else: + return None + + # Calculate average price for the day (all intervals, not just periods) + all_prices = [float(p["total"]) for p in today_prices] + avg_price = sum(all_prices) / len(all_prices) if all_prices else 0.0 + + # Build intervals with period grouping + periods = [] + current_period = [] for price_data in today_prices: starts_at = dt_util.parse_datetime(price_data["startsAt"]) if starts_at is None: continue - starts_at = dt_util.as_local(starts_at) - price_intervals.append( - { - "starts_at": starts_at, - "price": float(price_data["total"]), - "hour": starts_at.hour, - "minute": starts_at.minute, - } - ) - - # Sort by price (high to low for peak, low to high for best) - sorted_intervals = sorted(price_intervals, key=lambda x: x["price"], reverse=reverse_sort)[:5] - - # Format the result based on granularity - hourly_interval_minutes = 60 - result = [] - for interval in sorted_intervals: - if interval_minutes < hourly_interval_minutes: # More granular than hourly - result.append( + price = float(price_data["total"]) + if in_range(price): + current_period.append( { - "hour": interval["hour"], - "minute": interval["minute"], - "time": f"{interval['hour']:02d}:{interval['minute']:02d}", - "price": interval["price"], - } - ) - else: # Hourly data (for backward compatibility) - result.append( - { - "hour": interval["hour"], - "price": interval["price"], + "interval_hour": starts_at.hour, + "interval_minute": starts_at.minute, + "interval_time": f"{starts_at.hour:02d}:{starts_at.minute:02d}", + "price": price, + "interval_start": starts_at, + # interval_end will be filled later } ) + elif current_period: + periods.append(current_period) + current_period = [] + if current_period: + periods.append(current_period) - return {attribute_name: result} + # Add interval_end to each interval (next interval's start or None) + for period in periods: + for idx, interval in enumerate(period): + if idx + 1 < len(period): + interval["interval_end"] = period[idx + 1]["interval_start"] + else: + # Try to estimate end as start + interval_minutes + interval["interval_end"] = interval["interval_start"] + timedelta(minutes=interval_minutes) + + result = self._annotate_period_intervals(periods, ref_price, avg_price, interval_minutes) + + # Find the current or next interval (by time) from the annotated result + now = dt_util.now() + current_interval = None + for interval in result: + start = interval.get("interval_start") + end = interval.get("interval_end") + if start and end and start <= now < end: + current_interval = interval.copy() + break + else: + # If no current interval, show the next period's first interval (if available) + for interval in result: + start = interval.get("interval_start") + if start and start > now: + current_interval = interval.copy() + break + + attributes = {**current_interval} if current_interval else {} + attributes["intervals"] = result + return attributes def _get_price_hours_attributes(self, *, attribute_name: str, reverse_sort: bool) -> dict | None: """Get price hours attributes.""" diff --git a/custom_components/tibber_prices/config_flow.py b/custom_components/tibber_prices/config_flow.py index da33005..5390c3e 100644 --- a/custom_components/tibber_prices/config_flow.py +++ b/custom_components/tibber_prices/config_flow.py @@ -17,8 +17,12 @@ from .api import ( TibberPricesApiClientError, ) from .const import ( + CONF_BEST_PRICE_FLEX, CONF_EXTENDED_DESCRIPTIONS, + CONF_PEAK_PRICE_FLEX, + DEFAULT_BEST_PRICE_FLEX, DEFAULT_EXTENDED_DESCRIPTIONS, + DEFAULT_PEAK_PRICE_FLEX, DOMAIN, LOGGER, ) @@ -87,6 +91,28 @@ class TibberPricesFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): CONF_EXTENDED_DESCRIPTIONS, default=(user_input or {}).get(CONF_EXTENDED_DESCRIPTIONS, DEFAULT_EXTENDED_DESCRIPTIONS), ): selector.BooleanSelector(), + vol.Optional( + CONF_BEST_PRICE_FLEX, + default=(user_input or {}).get(CONF_BEST_PRICE_FLEX, DEFAULT_BEST_PRICE_FLEX), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, + max=20, + step=1, + mode=selector.NumberSelectorMode.SLIDER, + ), + ), + vol.Optional( + CONF_PEAK_PRICE_FLEX, + default=(user_input or {}).get(CONF_PEAK_PRICE_FLEX, DEFAULT_PEAK_PRICE_FLEX), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, + max=20, + step=1, + mode=selector.NumberSelectorMode.SLIDER, + ), + ), }, ), errors=_errors, @@ -105,10 +131,9 @@ class TibberPricesFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): class TibberPricesOptionsFlowHandler(config_entries.OptionsFlow): """Tibber Prices config flow options handler.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, _: config_entries.ConfigEntry) -> None: """Initialize options flow.""" super().__init__() - self.config_entry = config_entry async def async_step_init(self, user_input: dict | None = None) -> config_entries.ConfigFlowResult: """Manage the options.""" @@ -162,6 +187,38 @@ class TibberPricesOptionsFlowHandler(config_entries.OptionsFlow): self.config_entry.data.get(CONF_EXTENDED_DESCRIPTIONS, DEFAULT_EXTENDED_DESCRIPTIONS), ), ): selector.BooleanSelector(), + vol.Optional( + CONF_BEST_PRICE_FLEX, + default=int( + self.config_entry.options.get( + CONF_BEST_PRICE_FLEX, + self.config_entry.data.get(CONF_BEST_PRICE_FLEX, DEFAULT_BEST_PRICE_FLEX), + ) + ), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, + max=100, + step=1, + mode=selector.NumberSelectorMode.SLIDER, + ), + ), + vol.Optional( + CONF_PEAK_PRICE_FLEX, + default=int( + self.config_entry.options.get( + CONF_PEAK_PRICE_FLEX, + self.config_entry.data.get(CONF_PEAK_PRICE_FLEX, DEFAULT_PEAK_PRICE_FLEX), + ) + ), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, + max=100, + step=1, + mode=selector.NumberSelectorMode.SLIDER, + ), + ), } return self.async_show_form( diff --git a/custom_components/tibber_prices/const.py b/custom_components/tibber_prices/const.py index 5ac2c9f..40d9534 100644 --- a/custom_components/tibber_prices/const.py +++ b/custom_components/tibber_prices/const.py @@ -16,6 +16,8 @@ VERSION = "0.1.0" DOMAIN = "tibber_prices" CONF_ACCESS_TOKEN = "access_token" # noqa: S105 CONF_EXTENDED_DESCRIPTIONS = "extended_descriptions" +CONF_BEST_PRICE_FLEX = "best_price_flex" +CONF_PEAK_PRICE_FLEX = "peak_price_flex" ATTRIBUTION = "Data provided by Tibber" @@ -25,6 +27,8 @@ SCAN_INTERVAL = 60 * 5 # 5 minutes # Integration name should match manifest.json DEFAULT_NAME = "Tibber Price Information & Ratings" DEFAULT_EXTENDED_DESCRIPTIONS = False +DEFAULT_BEST_PRICE_FLEX = 5 # 5% flexibility for best price (user-facing, percent) +DEFAULT_PEAK_PRICE_FLEX = 5 # 5% flexibility for peak price (user-facing, percent) # Price level constants PRICE_LEVEL_NORMAL = "NORMAL" diff --git a/custom_components/tibber_prices/custom_translations/de.json b/custom_components/tibber_prices/custom_translations/de.json index ea404a6..bbcbaa3 100644 --- a/custom_components/tibber_prices/custom_translations/de.json +++ b/custom_components/tibber_prices/custom_translations/de.json @@ -116,30 +116,18 @@ } }, "binary_sensor": { - "peak_interval": { - "name": "Spitzenpreis-Intervall", + "peak_price_period": { + "name": "Spitzenpreis-Periode", "description": "Ob das aktuelle Intervall zu den teuersten des Tages gehört", "long_description": "Wird aktiviert, wenn der aktuelle Preis in den oberen 20% der heutigen Preise liegt", "usage_tips": "Nutze dies, um den Betrieb von Geräten mit hohem Verbrauch während teurer Intervalle zu vermeiden" }, - "best_price_interval": { - "name": "Bestpreis-Intervall", + "best_price_period": { + "name": "Bestpreis-Periode", "description": "Ob das aktuelle Intervall zu den günstigsten des Tages gehört", "long_description": "Wird aktiviert, wenn der aktuelle Preis in den unteren 20% der heutigen Preise liegt", "usage_tips": "Nutze dies, um Geräte mit hohem Verbrauch während der günstigsten Intervalle zu betreiben" }, - "peak_hour": { - "name": "Spitzenstunde", - "description": "Ob die aktuelle Stunde zu den teuersten des Tages gehört", - "long_description": "Wird aktiviert, wenn der aktuelle Preis in den oberen 20% der heutigen Preise liegt", - "usage_tips": "Nutze dies, um den Betrieb von Geräten mit hohem Verbrauch während teurer Stunden zu vermeiden" - }, - "best_price_hour": { - "name": "Beste-Preis-Stunde", - "description": "Ob die aktuelle Stunde zu den günstigsten des Tages gehört", - "long_description": "Wird aktiviert, wenn der aktuelle Preis in den unteren 20% der heutigen Preise liegt", - "usage_tips": "Nutze dies, um Geräte mit hohem Verbrauch während der günstigsten Stunden zu betreiben" - }, "connection": { "name": "Tibber API-Verbindung", "description": "Ob die Verbindung zur Tibber API funktioniert", @@ -147,4 +135,4 @@ "usage_tips": "Nutze dies, um den Verbindungsstatus zur Tibber API zu überwachen" } } -} +} \ No newline at end of file diff --git a/custom_components/tibber_prices/custom_translations/en.json b/custom_components/tibber_prices/custom_translations/en.json index 1294f99..c0007bc 100644 --- a/custom_components/tibber_prices/custom_translations/en.json +++ b/custom_components/tibber_prices/custom_translations/en.json @@ -116,30 +116,18 @@ } }, "binary_sensor": { - "peak_interval": { - "name": "Peak Price Interval", + "peak_price_period": { + "name": "Peak Price Periode", "description": "Whether the current interval is among the most expensive of the day", "long_description": "Turns on when the current price is in the top 20% of today's prices", "usage_tips": "Use this to avoid running high-consumption appliances during expensive intervals" }, - "best_price_interval": { - "name": "Best Price Interval", + "best_price_period": { + "name": "Best Price Periode", "description": "Whether the current interval is among the cheapest of the day", "long_description": "Turns on when the current price is in the bottom 20% of today's prices", "usage_tips": "Use this to run high-consumption appliances during the cheapest intervals" }, - "peak_hour": { - "name": "Peak Hour", - "description": "Whether the current hour is among the most expensive of the day", - "long_description": "Turns on when the current price is in the top 20% of today's prices", - "usage_tips": "Use this to avoid running high-consumption appliances during expensive hours" - }, - "best_price_hour": { - "name": "Best Price Hour", - "description": "Whether the current hour is among the cheapest of the day", - "long_description": "Turns on when the current price is in the bottom 20% of today's prices", - "usage_tips": "Use this to run high-consumption appliances during the cheapest hours" - }, "connection": { "name": "Tibber API Connection", "description": "Whether the connection to the Tibber API is working", @@ -147,4 +135,4 @@ "usage_tips": "Use this to monitor the connection status to the Tibber API" } } -} +} \ No newline at end of file diff --git a/custom_components/tibber_prices/sensor.py b/custom_components/tibber_prices/sensor.py index c09245c..d2ea309 100644 --- a/custom_components/tibber_prices/sensor.py +++ b/custom_components/tibber_prices/sensor.py @@ -13,7 +13,7 @@ from homeassistant.components.sensor import ( SensorEntity, SensorEntityDescription, ) -from homeassistant.const import CURRENCY_CENT, CURRENCY_EURO, PERCENTAGE, EntityCategory, UnitOfPower, UnitOfTime +from homeassistant.const import CURRENCY_EURO, PERCENTAGE, EntityCategory, UnitOfPower, UnitOfTime from homeassistant.util import dt as dt_util from .const import ( @@ -36,7 +36,7 @@ if TYPE_CHECKING: from .coordinator import TibberPricesDataUpdateCoordinator from .data import TibberPricesConfigEntry -PRICE_UNIT_CENT = CURRENCY_CENT + "/" + UnitOfPower.KILO_WATT + UnitOfTime.HOURS +PRICE_UNIT_CENT = "ct/" + UnitOfPower.KILO_WATT + UnitOfTime.HOURS PRICE_UNIT_EURO = CURRENCY_EURO + "/" + UnitOfPower.KILO_WATT + UnitOfTime.HOURS HOURS_IN_DAY = 24 LAST_HOUR_OF_DAY = 23 diff --git a/custom_components/tibber_prices/translations/de.json b/custom_components/tibber_prices/translations/de.json index 79a92e9..abc2181 100644 --- a/custom_components/tibber_prices/translations/de.json +++ b/custom_components/tibber_prices/translations/de.json @@ -5,7 +5,9 @@ "description": "Richte Tibber Preisinformationen & Bewertungen ein. Um ein API-Zugriffstoken zu generieren, besuche developer.tibber.com.", "data": { "access_token": "API-Zugriffstoken", - "extended_descriptions": "Erweiterte Beschreibungen in Entitätsattributen anzeigen" + "extended_descriptions": "Erweiterte Beschreibungen in Entitätsattributen anzeigen", + "best_price_flex": "Flexibilität für Bestpreis (%)", + "peak_price_flex": "Flexibilität für Spitzenpreis (%)" }, "title": "Tibber Preisinformationen & Bewertungen" } @@ -29,7 +31,9 @@ "description": "Konfiguriere Optionen für Tibber Preisinformationen & Bewertungen", "data": { "access_token": "Tibber Zugangstoken", - "extended_descriptions": "Erweiterte Beschreibungen in Entitätsattributen anzeigen" + "extended_descriptions": "Erweiterte Beschreibungen in Entitätsattributen anzeigen", + "best_price_flex": "Flexibilität für Bestpreis (%)", + "peak_price_flex": "Flexibilität für Spitzenpreis (%)" } } }, @@ -83,15 +87,15 @@ } }, "binary_sensor": { - "peak_hour": { - "name": "Spitzenstunde" + "peak_price_period": { + "name": "Spitzenperiode" }, - "best_price_hour": { - "name": "Beste-Preis-Stunde" + "best_price_period": { + "name": "Best-Preis-Periode" }, "connection": { "name": "Tibber API-Verbindung" } } } -} +} \ No newline at end of file diff --git a/custom_components/tibber_prices/translations/en.json b/custom_components/tibber_prices/translations/en.json index 57bdc41..8eb1da8 100644 --- a/custom_components/tibber_prices/translations/en.json +++ b/custom_components/tibber_prices/translations/en.json @@ -5,7 +5,9 @@ "description": "Set up Tibber Price Information & Ratings. To generate an API access token, visit developer.tibber.com.", "data": { "access_token": "API access token", - "extended_descriptions": "Show extended descriptions in entity attributes" + "extended_descriptions": "Show extended descriptions in entity attributes", + "best_price_flex": "Best Price Flexibility (%)", + "peak_price_flex": "Peak Price Flexibility (%)" }, "title": "Tibber Price Information & Ratings" } @@ -20,7 +22,9 @@ "abort": { "already_configured": "Integration is already configured", "entry_not_found": "Tibber configuration entry not found." - } + }, + "best_price_flex": "Best Price Flexibility (%)", + "peak_price_flex": "Peak Price Flexibility (%)" }, "options": { "step": { @@ -29,7 +33,9 @@ "description": "Configure options for Tibber Price Information & Ratings", "data": { "access_token": "Tibber Access Token", - "extended_descriptions": "Show extended descriptions in entity attributes" + "extended_descriptions": "Show extended descriptions in entity attributes", + "best_price_flex": "Best Price Flexibility (%)", + "peak_price_flex": "Peak Price Flexibility (%)" } } }, @@ -41,7 +47,9 @@ }, "abort": { "entry_not_found": "Tibber configuration entry not found." - } + }, + "best_price_flex": "Best Price Flexibility (%)", + "peak_price_flex": "Peak Price Flexibility (%)" }, "entity": { "sensor": { @@ -98,15 +106,15 @@ } }, "binary_sensor": { - "peak_hour": { - "name": "Peak Hour" + "peak_price_period": { + "name": "Peak Price Period" }, - "best_price_hour": { - "name": "Best Price Hour" + "best_price_period": { + "name": "Best Price Period" }, "connection": { "name": "Tibber API Connection" } } } -} +} \ No newline at end of file