From c9a7dcdae736dcf7e1626b76762c504c18185a3c Mon Sep 17 00:00:00 2001 From: Julian Pawlowski <75446+jpawlowski@users.noreply.github.com> Date: Thu, 4 Dec 2025 14:39:00 +0000 Subject: [PATCH] feat(services): add rolling window options with auto-zoom for ApexCharts Added two new rolling window options for get_apexcharts_yaml service to provide flexible dynamic chart visualization: - rolling_window: Fixed 48h window that automatically shifts between yesterday+today and today+tomorrow based on data availability - rolling_window_autozoom: Same as rolling_window but with progressive zoom-in (2h lookback + remaining time until midnight, updates every 15min) Implementation changes: - Updated service schema validation to accept new day options - Added entity mapping patterns for both rolling modes - Implemented minute-based graph_span calculation with quarter-hour alignment - Added config-template-card integration for dynamic span updates - Used current_interval_price sensor as 15-minute update trigger - Unified data loading: both rolling modes omit day parameter for dynamic selection - Applied ternary operator pattern for cleaner day_param logic - Made grid lines more subtle (borderColor #f5f5f5, strokeDashArray 0) Translation updates: - Added selector options in all 5 languages (de, en, nb, nl, sv) - Updated field descriptions to include default behavior and new options - Documented that rolling window is default when day parameter omitted Documentation updates: - Updated user docs (actions.md, automation-examples.md) with new options - Added detailed explanation of day parameter options - Included examples for both rolling_window and rolling_window_autozoom modes Impact: Users can now create auto-adapting ApexCharts that show 48h rolling windows with optional progressive zoom throughout the day. Requires config-template-card for dynamic behavior. --- custom_components/tibber_prices/services.yaml | 117 +------------- .../services/get_apexcharts_yaml.py | 143 ++++++++++++++++-- .../tibber_prices/translations/de.json | 10 +- .../tibber_prices/translations/en.json | 10 +- .../tibber_prices/translations/nb.json | 10 +- .../tibber_prices/translations/nl.json | 10 +- .../tibber_prices/translations/sv.json | 10 +- docs/user/actions.md | 12 +- docs/user/automation-examples.md | 22 ++- 9 files changed, 197 insertions(+), 147 deletions(-) diff --git a/custom_components/tibber_prices/services.yaml b/custom_components/tibber_prices/services.yaml index 4e22c60..676a0ea 100644 --- a/custom_components/tibber_prices/services.yaml +++ b/custom_components/tibber_prices/services.yaml @@ -1,54 +1,30 @@ get_price: - name: Get Price Data - description: >- - Fetch price data for a specific time range with automatic routing. Development and testing service for the price_info_for_range API function. Automatically uses PRICE_INFO, PRICE_INFO_RANGE, or both based on the time range boundary. fields: entry_id: - name: Entry ID - description: The config entry ID for the Tibber integration. required: true example: "1234567890abcdef" selector: config_entry: integration: tibber_prices start_time: - name: Start Time - description: Start of the time range (inclusive, timezone-aware). required: true example: "2025-11-01T00:00:00+01:00" selector: datetime: end_time: - name: End Time - description: End of the time range (exclusive, timezone-aware). required: true example: "2025-11-02T00:00:00+01:00" selector: datetime: get_apexcharts_yaml: - name: Get ApexCharts Card YAML - description: >- - ⚠️ IMPORTANT: This service generates a BASIC EXAMPLE configuration for ApexCharts Card as a starting point. It is NOT a complete solution for all ApexCharts features. - This integration is primarily a DATA PROVIDER. The generated YAML demonstrates how to use the `get_chartdata` service to fetch price data. Due to the segmented nature of our data (different time periods per series) and the use of Home Assistant's service API instead of entity attributes, many advanced ApexCharts features (like in_header, certain transformations) are not compatible or require manual customization. - - - You are welcome to customize the generated YAML for your specific needs, but please understand that comprehensive ApexCharts configuration support is beyond the scope of this integration. Community contributions with improved configurations are always appreciated - if you find a better setup that works, please share it so everyone can benefit! - - - For direct data access to build your own charts, use the `get_chartdata` service instead. fields: entry_id: - name: Entry ID - description: The config entry ID for the Tibber integration. required: true example: "1234567890abcdef" selector: config_entry: integration: tibber_prices day: - name: Day - description: >- - Which day to visualize (yesterday, today, or tomorrow). If not specified, returns a rolling 2-day window: today+tomorrow (when tomorrow data is available) or yesterday+today (when tomorrow data is not yet available). required: false example: today selector: @@ -57,11 +33,10 @@ get_apexcharts_yaml: - yesterday - today - tomorrow + - rolling_window + - rolling_window_autozoom translation_key: day level_type: - name: Level Type - description: >- - Select which price level classification to visualize: 'rating_level' (LOW/NORMAL/HIGH based on your configured thresholds) or 'level' (Tibber API levels: VERY_CHEAP/CHEAP/NORMAL/EXPENSIVE/VERY_EXPENSIVE). required: false default: rating_level example: rating_level @@ -72,24 +47,16 @@ get_apexcharts_yaml: - level translation_key: level_type highlight_best_price: - name: Highlight Best Price Periods - description: >- - Add a semi-transparent green overlay to highlight the best price periods on the chart. This makes it easy to visually identify the optimal times for energy consumption. required: false default: true example: true selector: boolean: get_chartdata: - name: Get Chart Data - description: >- - Returns price data in a chart-friendly format compatible with Tibber Core output. Works with ha-price-timeline-card, ApexCharts, Plotly, Mini Graph Card, and History Graph. Field names and structure are configurable. fields: general: fields: entry_id: - name: Entry ID - description: The config entry ID for the Tibber integration. required: true example: "1234567890abcdef" selector: @@ -99,9 +66,6 @@ get_chartdata: collapsed: true fields: day: - name: Day - description: >- - Which day(s) to fetch prices for. You can select multiple days. If not specified, returns a rolling 2-day window: today+tomorrow (when tomorrow data is available) or yesterday+today (when tomorrow data is not yet available). This provides continuous chart display without gaps. required: false selector: select: @@ -112,9 +76,6 @@ get_chartdata: multiple: true translation_key: day resolution: - name: Resolution - description: >- - Time resolution for the returned data. Options: 'interval' (default, 15-minute intervals, 96 points per day), 'hourly' (hourly averages, 24 points per day). required: false default: interval example: hourly @@ -128,9 +89,6 @@ get_chartdata: collapsed: true fields: level_filter: - name: Level Filter - description: >- - Filter intervals to include only specific Tibber price levels (very_cheap, cheap, normal, expensive, very_expensive). If not specified, all levels are included. required: false selector: select: @@ -143,9 +101,6 @@ get_chartdata: multiple: true translation_key: level_filter rating_level_filter: - name: Rating Level Filter - description: >- - Filter intervals to include only specific rating levels (low, normal, high). If not specified, all rating levels are included. required: false selector: select: @@ -156,9 +111,6 @@ get_chartdata: multiple: true translation_key: rating_level_filter period_filter: - name: Period Filter - description: >- - Filter intervals to include only those within Best Price or Peak Price periods. Options: 'best_price' (only intervals in Best Price periods), 'peak_price' (only intervals in Peak Price periods). If not specified, all intervals are included. This uses the precomputed period data from binary sensors (binary_sensor.best_price_period / binary_sensor.peak_price_period). required: false selector: select: @@ -170,18 +122,12 @@ get_chartdata: collapsed: true fields: minor_currency: - name: Minor Currency - description: >- - Return prices in minor currency units (cents for EUR, øre for NOK/SEK) instead of major currency units. Disabled by default. required: false default: false example: true selector: boolean: round_decimals: - name: Round Decimals - description: >- - Number of decimal places to round prices to (0-10). If not specified, uses default precision (4 decimals for major currency, 2 for minor currency). required: false example: 2 selector: @@ -190,12 +136,6 @@ get_chartdata: max: 10 mode: box insert_nulls: - name: Insert NULL Values - description: >- - NULL insertion mode for filtered data. - • none (default): only matching intervals - • segments: add NULLs at segment boundaries (clean gaps) - • all: NULL for every non-matching timestamp required: false default: none selector: @@ -206,19 +146,11 @@ get_chartdata: - all translation_key: insert_nulls connect_segments: - name: Connect Segments - description: >- - Only with insert_nulls='segments'. Adds boundary points to visually connect segments (stepline). - • Downward boundary: lower price at end of current segment - • Upward boundary: hold previous price before the gap required: false default: false selector: boolean: add_trailing_null: - name: Add Trailing Null Point - description: >- - Add a final data point with null values (except timestamp) at the end. Some chart libraries need this to prevent extrapolation/interpolation to the viewport edge when using stepline rendering. Leave disabled unless your chart requires it. required: false default: false selector: @@ -227,11 +159,6 @@ get_chartdata: collapsed: true fields: output_format: - name: Output Format - description: >- - Output format. - • array_of_objects (default): objects with configurable field names - • array_of_arrays: [timestamp, price] tuples required: false default: array_of_objects example: array_of_objects @@ -242,9 +169,6 @@ get_chartdata: - array_of_arrays translation_key: output_format data_key: - name: Data Key - description: >- - Custom name for the top-level data key in the response. Defaults to "data" if not specified. For ApexCharts compatibility with array_of_arrays, use "points". required: false example: prices selector: @@ -253,73 +177,46 @@ get_chartdata: collapsed: true fields: include_level: - name: Include Level - description: >- - Include Tibber price level (VERY_CHEAP … VERY_EXPENSIVE). required: false default: false example: true selector: boolean: include_rating_level: - name: Include Rating Level - description: >- - Include rating level (LOW/NORMAL/HIGH) based on configured thresholds. required: false default: false example: true selector: boolean: include_average: - name: Include Average - description: >- - Include daily average price. required: false default: false selector: boolean: start_time_field: - name: Start Time Field Name - description: >- - Custom name for the start time field in the output. Defaults to "start_time" if not specified. required: false example: time selector: text: end_time_field: - name: End Time Field Name - description: >- - Custom name for the end time field in the output. Defaults to "end_time" if not specified. Only used with period_filter. required: false example: end selector: text: price_field: - name: Price Field Name - description: >- - Custom name for the price field in the output. Defaults to "price_per_kwh" if not specified. required: false example: price selector: text: level_field: - name: Level Field Name - description: >- - Custom name for the level field in the output. Defaults to "level" if not specified. Only used when include_level is enabled. required: false selector: text: rating_level_field: - name: Rating Level Field Name - description: >- - Custom name for the rating_level field in the output. Defaults to "rating_level" if not specified. Only used when include_rating_level is enabled. required: false selector: text: average_field: - name: Average Field Name - description: >- - Custom name for the average field in the output. Defaults to "average" if not specified. Only used when include_average is enabled. required: false selector: text: @@ -327,22 +224,12 @@ get_chartdata: collapsed: true fields: array_fields: - name: Array Fields - description: >- - Choose extra fields to include using {field} syntax, comma-separated. - Available: start_time, price_per_kwh, level, rating_level, average. - Empty = default (timestamp + price). required: false selector: text: refresh_user_data: - name: Refresh User Data - description: >- - Forces a refresh of the user data (homes, profile information) from the Tibber API. This can be useful after making changes to your Tibber account or when troubleshooting connectivity issues. fields: entry_id: - name: Entry ID - description: The config entry ID for the Tibber integration. required: true example: "1234567890abcdef" selector: diff --git a/custom_components/tibber_prices/services/get_apexcharts_yaml.py b/custom_components/tibber_prices/services/get_apexcharts_yaml.py index 7b049f5..430da02 100644 --- a/custom_components/tibber_prices/services/get_apexcharts_yaml.py +++ b/custom_components/tibber_prices/services/get_apexcharts_yaml.py @@ -59,7 +59,7 @@ ATTR_ENTRY_ID: Final = "entry_id" APEXCHARTS_SERVICE_SCHEMA = vol.Schema( { vol.Required(ATTR_ENTRY_ID): cv.string, - vol.Optional("day"): vol.In(["yesterday", "today", "tomorrow"]), + vol.Optional("day"): vol.In(["yesterday", "today", "tomorrow", "rolling_window", "rolling_window_autozoom"]), vol.Optional("level_type", default="rating_level"): vol.In(["rating_level", "level"]), vol.Optional("highlight_best_price", default=True): cv.boolean, } @@ -92,6 +92,7 @@ def _build_entity_map( # Define mapping patterns for each combination of level_type and day # Note: Match by entity key (in unique_id), not entity_id (user can rename) # Note: For "yesterday", we use "today" sensors as they show current state + # Note: For "yesterday_today_tomorrow" and "today_tomorrow", we use "today" sensors (dynamic windows) pattern_map = { ("rating_level", "today"): [ ("lowest_price_today", [PRICE_RATING_LOW]), @@ -108,6 +109,16 @@ def _build_entity_map( ("average_price_tomorrow", [PRICE_RATING_NORMAL]), ("highest_price_tomorrow", [PRICE_RATING_HIGH]), ], + ("rating_level", "rolling_window"): [ + ("lowest_price_today", [PRICE_RATING_LOW]), + ("average_price_today", [PRICE_RATING_NORMAL]), + ("highest_price_today", [PRICE_RATING_HIGH]), + ], + ("rating_level", "rolling_window_autozoom"): [ + ("lowest_price_today", [PRICE_RATING_LOW]), + ("average_price_today", [PRICE_RATING_NORMAL]), + ("highest_price_today", [PRICE_RATING_HIGH]), + ], ("level", "today"): [ ("lowest_price_today", [PRICE_LEVEL_VERY_CHEAP, PRICE_LEVEL_CHEAP]), ("average_price_today", [PRICE_LEVEL_NORMAL]), @@ -123,6 +134,16 @@ def _build_entity_map( ("average_price_tomorrow", [PRICE_LEVEL_NORMAL]), ("highest_price_tomorrow", [PRICE_LEVEL_EXPENSIVE, PRICE_LEVEL_VERY_EXPENSIVE]), ], + ("level", "rolling_window"): [ + ("lowest_price_today", [PRICE_LEVEL_VERY_CHEAP, PRICE_LEVEL_CHEAP]), + ("average_price_today", [PRICE_LEVEL_NORMAL]), + ("highest_price_today", [PRICE_LEVEL_EXPENSIVE, PRICE_LEVEL_VERY_EXPENSIVE]), + ], + ("level", "rolling_window_autozoom"): [ + ("lowest_price_today", [PRICE_LEVEL_VERY_CHEAP, PRICE_LEVEL_CHEAP]), + ("average_price_today", [PRICE_LEVEL_NORMAL]), + ("highest_price_today", [PRICE_LEVEL_EXPENSIVE, PRICE_LEVEL_VERY_EXPENSIVE]), + ], } patterns = pattern_map.get((level_type, day), []) @@ -237,7 +258,8 @@ async def handle_apexcharts_yaml(call: ServiceCall) -> dict[str, Any]: # noqa: filter_param = f"level_filter: ['{level_key}']" # Conditionally include day parameter (omit for rolling window mode) - day_param = f"day: ['{day}'], " if day else "" + # For rolling_window and rolling_window_autozoom, omit day parameter (dynamic selection) + day_param = "" if day in ("rolling_window", "rolling_window_autozoom", None) else f"day: ['{day}'], " data_generator = ( f"const response = await hass.callWS({{ " @@ -282,7 +304,8 @@ async def handle_apexcharts_yaml(call: ServiceCall) -> dict[str, Any]: # noqa: # Create vertical highlight bands using separate Y-axis (0-1 range) # This creates a semi-transparent overlay from bottom to top without affecting price scale # Conditionally include day parameter (omit for rolling window mode) - day_param = f"day: ['{day}'], " if day else "" + # For rolling_window and rolling_window_autozoom, omit day parameter (dynamic selection) + day_param = "" if day in ("rolling_window", "rolling_window_autozoom", None) else f"day: ['{day}'], " # Store original prices for tooltip, but map to 1 for full-height overlay # We use a custom tooltip formatter to show the real price @@ -326,13 +349,13 @@ async def handle_apexcharts_yaml(call: ServiceCall) -> dict[str, Any]: # noqa: "Price Phases Daily Progress" if level_type == "rating_level" else "Price Level" ) - # Add translated day to title (only if day parameter was provided) - if day: + # Add translated day to title (only for fixed day views, not for dynamic modes) + if day and day not in ("rolling_window", "rolling_window_autozoom"): day_translated = get_translation(["selector", "day", "options", day], user_language) or day.capitalize() title = f"{title} - {day_translated}" # Configure span based on selected day - # For rolling window mode, use config-template-card for dynamic offset + # For rolling window modes, use config-template-card for dynamic config if day == "yesterday": span_config = {"start": "day", "offset": "-1d"} graph_span_value = None @@ -341,11 +364,22 @@ async def handle_apexcharts_yaml(call: ServiceCall) -> dict[str, Any]: # noqa: span_config = {"start": "day", "offset": "+1d"} graph_span_value = None use_template = False + elif day == "rolling_window": + # Rolling 48h window: yesterday+today OR today+tomorrow (shifts at 13:00) + span_config = None # Will be set in template + graph_span_value = "48h" + use_template = True + elif day == "rolling_window_autozoom": + # Rolling 48h window with auto-zoom: yesterday+today OR today+tomorrow (shifts at 13:00) + # Auto-zooms based on current time (2h lookback + remaining time) + span_config = None # Will be set in template + graph_span_value = None # Will be set in template + use_template = True elif day: # today (explicit) span_config = {"start": "day"} graph_span_value = None use_template = False - else: # Rolling window mode (None) + else: # Rolling window mode (None - same as rolling_window) # Use config-template-card to dynamically set offset based on data availability span_config = None # Will be set in template graph_span_value = "48h" @@ -390,8 +424,8 @@ async def handle_apexcharts_yaml(call: ServiceCall) -> dict[str, Any]: # noqa: }, "grid": { "show": True, - "borderColor": "#40475D", - "strokeDashArray": 4, + "borderColor": "#f5f5f5", + "strokeDashArray": 0, "xaxis": {"lines": {"show": True}}, "yaxis": {"lines": {"show": True}}, }, @@ -420,11 +454,8 @@ async def handle_apexcharts_yaml(call: ServiceCall) -> dict[str, Any]: # noqa: "series": series, } - # For rolling window mode, wrap in config-template-card for dynamic offset + # For rolling window mode and today_tomorrow, wrap in config-template-card for dynamic config if use_template: - # Add graph_span to base config (48h window) - result["graph_span"] = graph_span_value - # Find tomorrow_data_available binary sensor tomorrow_data_sensor = next( ( @@ -438,6 +469,81 @@ async def handle_apexcharts_yaml(call: ServiceCall) -> dict[str, Any]: # noqa: ) if tomorrow_data_sensor: + if day == "rolling_window_autozoom": + # rolling_window_autozoom mode: Dynamic graph_span with auto-zoom + # Shows last 120 min (8 intervals) + remaining minutes until end of time window + # Auto-zooms every 15 minutes when current interval completes + # When tomorrow data arrives after 13:00, extends to show tomorrow too + # + # Key principle: graph_span must always be divisible by 15 (full intervals) + # The current (running) interval stays included until it completes + # + # Calculation: + # 1. Round current time UP to next quarter-hour (include running interval) + # 2. Calculate minutes from end of running interval to midnight + # 3. Round to ensure full 15-minute intervals + # 4. Add 120min lookback (always 8 intervals) + # 5. If tomorrow data available: add 1440min (96 intervals) + # + # Example timeline (without tomorrow data): + # 08:00 → next quarter: 08:15 → to midnight: 945min → span: 120+945 = 1065min (71 intervals) + # 08:07 → next quarter: 08:15 → to midnight: 945min → span: 120+945 = 1065min (stays same) + # 08:15 → next quarter: 08:30 → to midnight: 930min → span: 120+930 = 1050min (70 intervals) + # 14:23 → next quarter: 14:30 → to midnight: 570min → span: 120+570 = 690min (46 intervals) + # + # After 13:00 with tomorrow data: + # 14:00 → next quarter: 14:15 → to midnight: 585min → span: 120+585+1440 = 2145min (143 intervals) + # 14:15 → next quarter: 14:30 → to midnight: 570min → span: 120+570+1440 = 2130min (142 intervals) + template_graph_span = ( + f"const now = new Date(); " + f"const currentMinute = now.getMinutes(); " + f"const nextQuarterMinute = Math.ceil(currentMinute / 15) * 15; " + f"const currentIntervalEnd = new Date(now); " + f"if (nextQuarterMinute === 60) {{ " + f" currentIntervalEnd.setHours(now.getHours() + 1, 0, 0, 0); " + f"}} else {{ " + f" currentIntervalEnd.setMinutes(nextQuarterMinute, 0, 0); " + f"}} " + f"const midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 0, 0, 0); " + f"const minutesFromIntervalEndToMidnight = Math.ceil((midnight - currentIntervalEnd) / 60000); " + f"const minutesRounded = Math.ceil(minutesFromIntervalEndToMidnight / 15) * 15; " + f"const lookback = 120; " + f"const hasTomorrowData = states['{tomorrow_data_sensor}'].state === 'on'; " + f"const totalMinutes = lookback + minutesRounded + (hasTomorrowData ? 1440 : 0); " + f"totalMinutes + 'min';" + ) + + # Find current_interval_price sensor for 15-minute update trigger + current_price_sensor = next( + ( + entity.entity_id + for entity in entity_registry.entities.values() + if entity.config_entry_id == entry_id + and entity.unique_id + and entity.unique_id.endswith("_current_interval_price") + ), + None, + ) + + trigger_entities = [tomorrow_data_sensor] + if current_price_sensor: + trigger_entities.append(current_price_sensor) + + return { + "type": "custom:config-template-card", + "variables": { + "v_graph_span": template_graph_span, + }, + "entities": trigger_entities, + "card": { + **result, + "span": {"start": "minute", "offset": "-120min"}, + "graph_span": "${v_graph_span}", + }, + } + # Rolling window modes (day is None or rolling_window): Dynamic offset + # Add graph_span to base config (48h window) + result["graph_span"] = graph_span_value # Wrap in config-template-card with dynamic offset calculation # Template checks if tomorrow data is available (binary sensor state) # If 'on' (tomorrow data available) → offset +1d (show today+tomorrow) @@ -458,8 +564,15 @@ async def handle_apexcharts_yaml(call: ServiceCall) -> dict[str, Any]: # noqa: }, } - # Fallback if sensor not found: just use +1d offset - result["span"] = {"end": "day", "offset": "+1d"} + # Fallback if sensor not found + if day == "rolling_window_autozoom": + # Fallback: show today with 24h span + result["span"] = {"start": "day"} + result["graph_span"] = "24h" + else: + # Rolling window fallback (rolling_window or None): just use +1d offset + result["span"] = {"end": "day", "offset": "+1d"} + result["graph_span"] = "48h" return result # Add span for fixed-day views diff --git a/custom_components/tibber_prices/translations/de.json b/custom_components/tibber_prices/translations/de.json index f81c1fc..f01d2cc 100644 --- a/custom_components/tibber_prices/translations/de.json +++ b/custom_components/tibber_prices/translations/de.json @@ -861,11 +861,15 @@ }, "day": { "name": "Tag", - "description": "Welcher Tag visualisiert werden soll (gestern, heute oder morgen). Falls nicht angegeben, wird ein rollierendes 2-Tage-Fenster zurückgegeben: heute+morgen (wenn Daten für morgen verfügbar sind) oder gestern+heute (wenn Daten für morgen noch nicht verfügbar sind)." + "description": "Welcher Tag visualisiert werden soll (Standard: Rollierendes Fenster). Feste Tag-Optionen (Gestern/Heute/Morgen) zeigen 24h-Fenster ohne zusätzliche Abhängigkeiten. Dynamische Optionen benötigen config-template-card: Rollierendes Fenster zeigt ein festes 48h-Fenster, das automatisch zwischen gestern+heute und heute+morgen wechselt basierend auf Datenverfügbarkeit. Rollierendes Fenster (Auto-Zoom) verhält sich gleich, zoomt aber zusätzlich automatisch rein (2h Rückblick + verbleibende Zeit bis Mitternacht, graph_span verringert sich alle 15 Minuten)." }, "level_type": { "name": "Stufen-Typ", "description": "Wähle, welche Preisstufen-Klassifizierung visualisiert werden soll: 'rating_level' (niedrig/normal/hoch basierend auf deinen konfigurierten Schwellenwerten) oder 'level' (Tibber-API-Stufen: sehr günstig/günstig/normal/teuer/sehr teuer)." + }, + "highlight_best_price": { + "name": "Bestpreis-Zeiträume hervorheben", + "description": "Füge eine halbtransparente grüne Überlagerung hinzu, um die Bestpreis-Zeiträume im Diagramm hervorzuheben. Dies erleichtert die visuelle Identifizierung der optimalen Zeiten für den Energieverbrauch." } } }, @@ -1018,7 +1022,9 @@ "options": { "yesterday": "Gestern", "today": "Heute", - "tomorrow": "Morgen" + "tomorrow": "Morgen", + "rolling_window": "Rollierendes Fenster", + "rolling_window_autozoom": "Rollierendes Fenster (Auto-Zoom)" } }, "resolution": { diff --git a/custom_components/tibber_prices/translations/en.json b/custom_components/tibber_prices/translations/en.json index 23655bf..64394d1 100644 --- a/custom_components/tibber_prices/translations/en.json +++ b/custom_components/tibber_prices/translations/en.json @@ -857,11 +857,15 @@ }, "day": { "name": "Day", - "description": "Which day to visualize (yesterday, today, or tomorrow). If not specified, returns a rolling 2-day window: today+tomorrow (when tomorrow data is available) or yesterday+today (when tomorrow data is not yet available)." + "description": "Which day to visualize (default: Rolling Window). Fixed day options (Yesterday/Today/Tomorrow) show 24h spans without additional dependencies. Dynamic options require config-template-card: Rolling Window displays a fixed 48h window that automatically shifts between yesterday+today and today+tomorrow based on data availability. Rolling Window (Auto-Zoom) behaves the same but additionally auto-zooms in (2h lookback + remaining time until midnight, graph_span decreases every 15 minutes)." }, "level_type": { "name": "Level Type", "description": "Select which price level classification to visualize: 'rating_level' (low/normal/high based on your configured thresholds) or 'level' (Tibber API levels: very cheap/cheap/normal/expensive/very expensive)." + }, + "highlight_best_price": { + "name": "Highlight Best Price Periods", + "description": "Add a semi-transparent green overlay to highlight the best price periods on the chart. This makes it easy to visually identify the optimal times for energy consumption." } } }, @@ -1014,7 +1018,9 @@ "options": { "yesterday": "Yesterday", "today": "Today", - "tomorrow": "Tomorrow" + "tomorrow": "Tomorrow", + "rolling_window": "Rolling Window", + "rolling_window_autozoom": "Rolling Window (Auto-Zoom)" } }, "resolution": { diff --git a/custom_components/tibber_prices/translations/nb.json b/custom_components/tibber_prices/translations/nb.json index 6d9ef81..4b8d5c5 100644 --- a/custom_components/tibber_prices/translations/nb.json +++ b/custom_components/tibber_prices/translations/nb.json @@ -857,11 +857,15 @@ }, "day": { "name": "Dag", - "description": "Hvilken dag som skal visualiseres (i går, i dag eller i morgen). Hvis ikke angitt, returneres et rullende 2-dagers vindu: i dag+i morgen (når data for i morgen er tilgjengelig) eller i går+i dag (når data for i morgen ikke er tilgjengelig ennå)." + "description": "Hvilken dag som skal visualiseres (standard: Rullerende vindu). Faste dagalternativer (I går/I dag/I morgen) viser 24t-spenn uten ekstra avhengigheter. Dynamiske alternativer krever config-template-card: Rullerende vindu lager et fast 48t-vindu som automatisk skifter mellom i går+i dag og i dag+i morgen basert på datatilgjengelighet. Rullerende vindu (Auto-Zoom) oppfører seg likt, men zoomer i tillegg automatisk inn (2t tilbakeblikk + gjenværende tid til midnatt, graph_span reduseres hvert 15. minutt)." }, "level_type": { "name": "Nivåtype", "description": "Velg hvilken prisnivåklassifisering som skal visualiseres: 'rating_level' (lav/normal/høy basert på dine konfigurerte terskelverdier) eller 'level' (Tibber API-nivåer: veldig billig/billig/normal/dyr/veldig dyr)." + }, + "highlight_best_price": { + "name": "Fremhev beste prisperioder", + "description": "Legg til et halvgjennomsiktig grønt overlegg for å fremheve de beste prisperiodene i diagrammet. Dette gjør det enkelt å visuelt identifisere de optimale tidene for energiforbruk." } } }, @@ -1014,7 +1018,9 @@ "options": { "yesterday": "I går", "today": "I dag", - "tomorrow": "I morgen" + "tomorrow": "I morgen", + "rolling_window": "Rullerende vindu", + "rolling_window_autozoom": "Rullerende vindu (Auto-Zoom)" } }, "resolution": { diff --git a/custom_components/tibber_prices/translations/nl.json b/custom_components/tibber_prices/translations/nl.json index 0002430..42e79f7 100644 --- a/custom_components/tibber_prices/translations/nl.json +++ b/custom_components/tibber_prices/translations/nl.json @@ -857,11 +857,15 @@ }, "day": { "name": "Dag", - "description": "Welke dag gevisualiseerd moet worden (gisteren, vandaag of morgen). Indien niet opgegeven, wordt een rollend 2-dagen venster geretourneerd: vandaag+morgen (wanneer gegevens voor morgen beschikbaar zijn) of gisteren+vandaag (wanneer gegevens voor morgen nog niet beschikbaar zijn)." + "description": "Welke dag gevisualiseerd moet worden (standaard: Rollend venster). Vaste dagopties (Gisteren/Vandaag/Morgen) tonen 24u-vensters zonder extra afhankelijkheden. Dynamische opties vereisen config-template-card: Rollend venster creëert een vast 48u-venster dat automatisch wisselt tussen gisteren+vandaag en vandaag+morgen op basis van databeschikbaarheid. Rollend venster (Auto-Zoom) gedraagt zich hetzelfde maar zoomt bovendien automatisch in (2u terugkijken + resterende tijd tot middernacht, graph_span neemt elke 15 minuten af)." }, "level_type": { "name": "Niveautype", "description": "Selecteer welke prijsniveauclassificatie gevisualiseerd moet worden: 'rating_level' (laag/normaal/hoog op basis van jouw geconfigureerde drempelwaarden) of 'level' (Tibber API-niveaus: zeer goedkoop/goedkoop/normaal/duur/zeer duur)." + }, + "highlight_best_price": { + "name": "Beste prijsperiodes markeren", + "description": "Voeg een halfdo0rzichtige groene overlay toe om de beste prijsperiodes in de grafiek te markeren. Dit maakt het gemakkelijk om visueel de optimale tijden voor energieverbruik te identificeren." } } }, @@ -1014,7 +1018,9 @@ "options": { "yesterday": "Gisteren", "today": "Vandaag", - "tomorrow": "Morgen" + "tomorrow": "Morgen", + "rolling_window": "Rollend venster", + "rolling_window_autozoom": "Rollend venster (Auto-Zoom)" } }, "resolution": { diff --git a/custom_components/tibber_prices/translations/sv.json b/custom_components/tibber_prices/translations/sv.json index b909f64..a3146b7 100644 --- a/custom_components/tibber_prices/translations/sv.json +++ b/custom_components/tibber_prices/translations/sv.json @@ -857,11 +857,15 @@ }, "day": { "name": "Dag", - "description": "Vilken dag som ska visualiseras (igår, idag eller imorgon). Om inte angivet returneras ett rullande 2-dagarsfönster: idag+imorgon (när data för imorgon är tillgänglig) eller igår+idag (när data för imorgon inte är tillgänglig ännu)." + "description": "Vilken dag som ska visualiseras (standard: Rullande fönster). Fasta dagalternativ (Igår/Idag/Imorgon) visar 24t-spann utan extra beroenden. Dynamiska alternativ kräver config-template-card: Rullande fönster skapar ett fast 48t-fönster som automatiskt växlar mellan igår+idag och idag+imorgon baserat på datatillgänglighet. Rullande fönster (Auto-Zoom) beter sig likadant men zoomar dessutom automatiskt in (2t tillbakablick + återstående tid till midnatt, graph_span minskar varje kvart)." }, "level_type": { "name": "Nivåtyp", "description": "Välj vilken prisnivåklassificering som ska visualiseras: 'rating_level' (låg/normal/hög baserat på dina konfigurerade tröskelvärden) eller 'level' (Tibber API-nivåer: mycket billig/billig/normal/dyr/mycket dyr)." + }, + "highlight_best_price": { + "name": "Markera bästa prisperioder", + "description": "Lägg till ett halvgenomskinligt grönt överlägg för att markera de bästa prisperioderna i diagrammet. Detta gör det enkelt att visuellt identifiera de optimala tiderna för energiförbrukning." } } }, @@ -1014,7 +1018,9 @@ "options": { "yesterday": "Igår", "today": "Idag", - "tomorrow": "Imorgon" + "tomorrow": "Imorgon", + "rolling_window": "Rullande fönster", + "rolling_window_autozoom": "Rullande fönster (Auto-Zoom)" } }, "resolution": { diff --git a/docs/user/actions.md b/docs/user/actions.md index 7602735..8f15117 100644 --- a/docs/user/actions.md +++ b/docs/user/actions.md @@ -132,15 +132,17 @@ For detailed parameter descriptions, open **Developer Tools → Actions** (the U service: tibber_prices.get_apexcharts_yaml data: entry_id: YOUR_ENTRY_ID - day: today # Optional: omit for rolling 48h window (requires config-template-card) + day: today # Optional: yesterday, today, tomorrow, rolling_window, rolling_window_autozoom response_variable: apexcharts_config ``` -**Rolling Window Mode:** When omitting the `day` parameter, the action generates a dynamic 48-hour rolling window that automatically shows: -- Today + Tomorrow (when tomorrow data is available) -- Yesterday + Today (when tomorrow data is not yet available) +**Day Parameter Options:** -This mode requires the Config Template Card to dynamically adjust the time window based on data availability. +- **Fixed days** (`yesterday`, `today`, `tomorrow`): Static 24-hour views, no additional dependencies +- **Rolling Window** (default when omitted or `rolling_window`): Dynamic 48-hour window that automatically shifts between yesterday+today and today+tomorrow based on data availability +- **Rolling Window (Auto-Zoom)** (`rolling_window_autozoom`): Same as rolling window, but additionally zooms in progressively (2h lookback + remaining time until midnight, graph span decreases every 15 minutes) + +**Note:** Rolling window modes require [Config Template Card](https://github.com/iantrich/config-template-card) for dynamic behavior. Use the response in Lovelace dashboards by copying the generated YAML. diff --git a/docs/user/automation-examples.md b/docs/user/automation-examples.md index ecde2c4..f7a2705 100644 --- a/docs/user/automation-examples.md +++ b/docs/user/automation-examples.md @@ -285,7 +285,7 @@ For a dynamic chart that automatically adapts to data availability: service: tibber_prices.get_apexcharts_yaml data: entry_id: YOUR_ENTRY_ID - # Omit 'day' parameter for rolling window + day: rolling_window # Or omit for same behavior (default) level_type: rating_level response_variable: apexcharts_config ``` @@ -293,8 +293,26 @@ response_variable: apexcharts_config **Behavior:** - **When tomorrow data available** (typically after ~13:00): Shows today + tomorrow - **When tomorrow data not available**: Shows yesterday + today +- **Fixed 48h span:** Always shows full 48 hours -**Note:** Rolling window mode requires Config Template Card to dynamically adjust the time range. +**Auto-Zoom Variant:** + +For progressive zoom-in throughout the day: + +```yaml +service: tibber_prices.get_apexcharts_yaml +data: + entry_id: YOUR_ENTRY_ID + day: rolling_window_autozoom + level_type: rating_level +response_variable: apexcharts_config +``` + +- Same data loading as rolling window +- **Progressive zoom:** Graph span starts at ~26h in the morning and decreases to ~14h by midnight +- **Updates every 15 minutes:** Always shows 2h lookback + remaining time until midnight + +**Note:** Rolling window modes require Config Template Card to dynamically adjust the time range. ### Features