Add entity_resolver module that lets all service parameters accept
HA entity references in place of literal values. The entity's current
state (or a specific attribute via the @attr syntax) is resolved at
call time and coerced to the expected Python type.
Syntax:
"sensor.washing_duration" → uses entity state
"sensor.washing_duration@run_minutes" → uses entity attribute
Apply or_entity_ref() and resolve_entity_references() to all five
service handlers (get_price, find_cheapest_block, find_cheapest_hours,
find_cheapest_schedule, get_chartdata) for every parameter where a
dynamic value from another entity is useful (duration, start/end times,
offsets, etc.).
Add five new translation keys for entity-resolution error messages
(invalid_entity_reference, entity_not_found, entity_attribute_not_found,
entity_state_unavailable, entity_value_conversion_failed) across all
five language files.
Fix pytest warning filter to suppress AsyncMock cleanup noise, and
update test_resource_cleanup to mock hass.config_entries.async_entries
so the blueprint-removal path in async_remove_entry does not raise.
Impact: Automations and scripts can pass sensor entity IDs as service
parameters (e.g. duration from a sensor) instead of having to use
template-based workarounds.
_resolve_time_with_day_offset() was calling dt_util.now() internally
instead of using the injected now parameter. This caused incorrect date
calculations in tests and any caller that passes a specific reference time.
Also add missing price_rank_* sensor keys to TIME_SENSITIVE_ENTITY_KEYS
in coordinator/constants.py so quarter-hour refresh is registered for all
11 price rank sensors (current/next/previous interval and hour variants).
Rename dt as dt_utils → dt as dt_util (ICN001) across 11 files to follow
the project-wide import alias convention. Apply ruff auto-fixes for import
ordering and collapsing single-item imports throughout the codebase.
Released-Bug: no
Improved validation logic for service parameters in find_cheapest_hours, find_cheapest_schedule, and chartdata services. Added checks for unique task names, ensured that segment durations do not exceed total duration, and clarified error messages for better user understanding.
Impact: Users will receive clearer error messages and improved validation when using the services, leading to a more robust experience.
Updated the filter logic to include period_filter alongside level_filter and rating_level_filter for segment definitions. This change ensures that users can utilize period_filter effectively when defining segments.
Impact: Users can now use period_filter in addition to existing filters for more flexible segment definitions.
New services for finding optimal electricity price windows:
- find_cheapest_block: Cheapest contiguous time block (e.g., dishwasher)
- find_cheapest_hours: Cheapest N hours, non-contiguous (e.g., EV charging)
- find_cheapest_schedule: Multi-task scheduling with no-overlap (e.g., shared circuit)
- find_most_expensive_block: Most expensive contiguous block (peak avoidance)
- find_most_expensive_hours: Most expensive N hours (consumption shifting)
Key features:
- Flexible search range (today, tomorrow, today+tomorrow, rolling window)
- Power profile support for variable consumption patterns
- Price level filtering (e.g., only CHEAP/VERY_CHEAP intervals)
- Comparison details showing savings vs. alternatives
- Sliding window algorithm (O(n)) for block search, greedy scheduling
for multi-task optimization
Also includes:
- Shared validation utilities (search range, price level, power profile)
- entry_id now optional on all services (auto-selects single home)
- Input validation for existing services (time range, filter conflicts)
- Service icons for all new and existing services
- Translations for all 5 languages (en, de, nb, nl, sv)
- Removed 10 unused config.error translation keys (replaced by exceptions)
- Tests for price window algorithms and search range resolution
Impact: Users can find optimal time windows for appliances, EV charging,
and multi-device scheduling via HA service calls. Existing services
improved with optional entry_id and better input validation.
Add four optional parameters to the get_chartdata service:
- include_energy: Include raw energy/spot price (default: false)
- include_tax: Include tax component (default: false)
- energy_field: Custom field name (default: energy_price)
- tax_field: Custom field name (default: tax)
Custom field names allow direct compatibility with ApexCharts
and other charting tools without post-processing.
All code paths (all/segments/none insert_nulls modes) and the
last-interval handler include energy/tax when enabled.
Added translations for all 5 languages (en, de, nl, nb, sv).
Impact: Users can include price composition data in chart exports,
enabling visual breakdowns of energy cost vs. taxes in dashboards.
Add resolution parameter to get_chartdata and get_apexcharts_yaml services,
allowing users to choose between original 15-minute intervals or aggregated
hourly values for chart visualization.
Implementation uses rolling 5-interval window aggregation (-2, -1, 0, +1, +2
around :00 of each hour = 60 minutes total), matching the sensor rolling
hour methodology. Respects user's CONF_AVERAGE_SENSOR_DISPLAY setting for
mean vs median calculation.
Changes:
- formatters.py: Add aggregate_to_hourly() function preserving original
field names (startsAt, total, level, rating_level) for unified processing
- get_chartdata.py: Pre-aggregate data before processing when resolution is
'hourly', enabling same code path for filters/insert_nulls/connect_segments
- get_apexcharts_yaml.py: Add resolution parameter, pass to all 4 get_chartdata
service calls in generated JavaScript
- services.yaml: Add resolution field with interval/hourly selector
- icons.json: Add section icons for get_apexcharts_yaml fields
- translations: Add highlight_peak_price and resolution field translations
for all 5 languages (en, de, sv, nb, nl)
Impact: Users can now generate cleaner charts with 24 hourly data points
instead of 96 quarter-hourly intervals. The unified processing approach
ensures all chart features (filters, null insertion, segment connection)
work identically for both resolutions.
Changed from fixed padding (0.5ct below min, 1ct above max) to
proportional padding based on data range (8% below, 15% above).
This ensures consistent visual "airiness" across all price ranges,
whether prices are at 30ct or 150ct. Both subunit (ct/øre) and
base currency (€/kr) now use the same proportional logic.
Previous fixed padding looked too tight on charts with large price
ranges (e.g., 0.6€-1.5€) compared to charts with small ranges
(e.g., 28-35ct).
Impact: Chart metadata sensor provides better-scaled yaxis_min/yaxis_max
values for all chart cards, making price visualizations more readable
with appropriate whitespace around data regardless of price range.
Problem: In segmented price charts with connect_segments=true, vertical lines
at price level transitions were always drawn by the ending segment. This meant
a price INCREASE showed a cheap-colored line going UP, and a price DECREASE
showed an expensive-colored line going DOWN - counterintuitive for users.
Solution: Implement directional bridge-point logic using price level hierarchy:
- Add _is_transition_to_more_expensive() helper using PRICE_LEVEL_MAPPING and
PRICE_RATING_MAPPING to determine transition direction
- Price INCREASE (cheap → expensive): The MORE EXPENSIVE segment draws the
vertical line UP via new start-bridge logic (end-bridge at segment start)
- Price DECREASE (expensive → cheap): The MORE EXPENSIVE segment draws the
vertical line DOWN via existing end-bridge logic (bridge at segment end)
Technical changes:
- Track prev_value and prev_price for segment start detection
- Add end-bridge points at segment starts for upward transitions
- Replace unconditional bridge points with directional hold/bridge logic
- Hold points extend segment horizontally when next segment handles transition
Impact: Vertical transition lines now consistently use the color of the more
expensive price level, making price movements more visually intuitive.
Changed from iterating over each day separately to collecting all
intervals for selected days into one continuous list before processing.
Changes:
- Collect all intervals via get_intervals_for_day_offsets() with all
day_offsets at once
- Remove outer `for day in days:` loop around interval processing
- Build date->day_key mapping during average calculation for lookup
- Add _get_day_key_for_interval() helper for average_field assignment
- Simplify midnight handling: only extend at END of entire selection
- Remove complex "next day lookup" logic at midnight boundaries
The segment boundary handling (bridge points, NULL insertion) now works
automatically across midnight since intervals are processed as one list.
Impact: Fixes bridge point rendering at midnight when rating levels
change between days. Simplifies code structure by removing ~60 lines
of per-day midnight-specific logic.
Fixes configuration wizard not saving settings (#59):
Root cause was twofold:
1. Linear multi-step flow pattern didn't properly persist changes between steps
2. Best/peak price settings used nested sections format - values were saved
in sections (period_settings, flexibility_settings, etc.) but read from
flat structure, causing configured values to be ignored on subsequent runs
Solution:
- Replaced linear step-through flow with menu-based navigation system
- Each configuration area now has dedicated "Save & Back" buttons
- Removed nested sections from all steps except best/peak price (where they
provide better UX for grouping related settings)
- Fixed best/peak price steps to correctly extract values from sections:
period_settings, flexibility_settings, relaxation_and_target_periods
- Added reset-to-defaults functionality with confirmation dialog
UI/UX improvements:
- Menu structure: General Settings, Currency Display, Price Rating Thresholds,
Volatility, Best Price Period, Peak Price Period, Price Trend,
Chart Data Export, Reset to Defaults, Back
- Removed confusing step progress indicators ("{step_num} / {total_steps}")
- Changed all submit buttons from "Continue →" to "↩ Save & Back"
- Clear grouping of settings by functional area
Translation updates (nl.json + sv.json):
- Refined volatility threshold descriptions with CV formula explanations
- Clarified price trend thresholds (compares current vs. future N-hour average,
not "per hour increase")
- Standardized terminology (e.g., "entry" → "item", compound word consistency)
- Consistently formatted all sensor names and descriptions
- Added new data lifecycle status sensor names
Technical changes:
- Options flow refactored from linear to menu pattern with menu_options dict
- New reset_to_defaults step with confirmation and abort handlers
- Section extraction logic in best_price/peak_price steps now correctly reads
from nested structure (period_settings.*, flexibility_settings.*, etc.)
- Removed sections from general_settings, display_settings, volatility, etc.
(simpler flat structure via menu navigation)
Impact: Configuration wizard now reliably saves all settings. Users can
navigate between setting areas without restarting the flow. Reset function
enables quick recovery when experimenting with thresholds. Previously
configured best/peak price settings are now correctly applied.
Implemented comprehensive metadata calculation for chart data export service
with automatic Y-axis scaling and gradient positioning based on actual price
statistics.
Changes:
- Added 'metadata' parameter to get_chartdata service (include/only/none)
- Implemented _calculate_metadata() with per-day price statistics
* min/max/avg/median prices
* avg_position and median_position (0-1 scale for gradient stops)
* yaxis_suggested bounds (floor(min)-1, ceil(max)+1)
* time_range with day boundaries
* currency info with symbol and unit
- Integrated metadata into rolling_window modes via config-template-card
* Pre-calculated yaxis bounds (no async issues in templates)
* Dynamic gradient stops based on avg_position
* Server-side calculation ensures consistency
Visual refinements:
- Best price overlay opacity reduced to 0.05 (ultra-subtle green hint)
- Stroke width increased to 1.5 for better visibility
- Gradient opacity adjusted to 0.45 with "light" shade
- Marker configuration: size 0, hover size 2, strokeWidth 1
- Header display: Only show LOW/HIGH rating_levels (min/max prices)
* Conditional logic excludes NORMAL and level types
* Entity state shows meaningful extrema values
- NOW marker label removed for rolling_window_autozoom mode
* Static position at 120min lookback makes label misleading
Code cleanup:
- Removed redundant all_series_config (server-side data formatting)
- Currency names capitalized (Cents, Øre, Öre, Pence)
Translation updates:
- Added metadata selector translations (de, en, nb, nl, sv)
- Added metadata field description in services
- Synchronized all language files
Impact: Users get dynamic Y-axis scaling based on actual price data,
eliminating manual configuration. Rolling window charts automatically
adjust axis bounds and gradient positioning. Header shows only
meaningful extreme values (daily min/max). All data transformation
happens server-side for optimal performance and consistency.
Period data in array_of_arrays format now generates proper segment structure
for stepline charts. Each period produces 2-3 data points depending on
insert_nulls parameter:
1. Start time with price (begin period)
2. End time with price (hold price level)
3. End time with NULL (terminate segment, only if insert_nulls='segments'/'all')
This enables ApexCharts to correctly display periods as continuous blocks with
clean gaps between them. Previously only start point was generated, causing
periods to render as single points instead of continuous segments.
Changes:
- formatters.py: Updated get_period_data() to generate 2-3 points per period
- formatters.py: Added insert_nulls parameter to control NULL termination
- get_chartdata.py: Pass insert_nulls parameter to get_period_data()
- get_apexcharts_yaml.py: Set insert_nulls='segments' for period overlay
- get_apexcharts_yaml.py: Preserve NULL values in data_generator mapping
- get_apexcharts_yaml.py: Store original price for potential tooltip access
- tests: Added comprehensive period data format tests
Impact: Best price and peak price period overlays now display correctly as
continuous blocks with proper segment separation in ApexCharts cards.
Add dynamic rolling window mode to get_chartdata and get_apexcharts_yaml
services that automatically adapts to data availability.
When 'day' parameter is omitted, services return 48-hour window:
- With tomorrow data (after ~13:00): today + tomorrow
- Without tomorrow data: yesterday + today
Changes:
- Implement rolling window logic in get_chartdata using has_tomorrow_data()
- Generate config-template-card wrapper in get_apexcharts_yaml for dynamic
ApexCharts span.offset based on tomorrow_data_available binary sensor
- Update service descriptions in services.yaml
- Add rolling window descriptions to all translations (de, en, nb, nl, sv)
- Document rolling window mode in docs/user/services.md
- Add ApexCharts examples with prerequisites in docs/user/automation-examples.md
BREAKING CHANGE: get_apexcharts_yaml rolling window mode requires
config-template-card in addition to apexcharts-card for dynamic offset
calculation.
Impact: Users can create auto-adapting 48h price charts without manual day
selection. Fixed day views (day: today/yesterday/tomorrow) still work with
apexcharts-card only.
Simplifies the connect_segments implementation to use a unified bridge-point
approach for all price transitions (up/down/same). Previously used
direction-dependent logic (hold vs connect points) which was unnecessarily
complex.
Changes:
- get_chartdata.py: Bridge points now always use next interval's price at
boundary timestamp, creating smooth visual connection between segments
- get_chartdata.py: Trailing NULL removal now conditional on insert_nulls mode
('segments' removes for header fix, 'all' preserves intentional gaps)
- get_apexcharts_yaml.py: Enable connect_segments by default, activate
show_states for header min/max display
- get_apexcharts_yaml.py: Remove extrema series (not compatible with
data_generator approach - ApexCharts requires entity time-series data)
- tests: Move test_connect_segments.py to tests/services/ to mirror source
structure
Impact: ApexCharts cards now show clean visual connections between price level
segments with proper header statistics display. Trailing NULLs no longer cause
"N/A" in headers for filtered data. Test organization improved for
maintainability.
Renamed service modules for consistency with service identifiers:
- apexcharts.py → get_apexcharts_yaml.py
- chartdata.py → get_chartdata.py
- Added: get_price.py (new service module)
Naming convention: Module names now match service names directly
(tibber_prices.get_apexcharts_yaml → get_apexcharts_yaml.py)
Impact: Improved code organization, easier to locate service implementations.
No functional changes.
2025-11-25 20:44:39 +00:00
Renamed from custom_components/tibber_prices/services/chartdata.py (Browse further)