- coordinator/core.py: Fix return type for _get_threshold_percentages()
- coordinator/data_transformation.py: Add type ignore for cached data return
- sensor/core.py: Initialize _state_info with required unrecorded_attributes
Add calculate_coefficient_of_variation() as central utility function:
- CV = (std_dev / mean) * 100 as standardized volatility measure
- calculate_volatility_with_cv() returns both level and numeric CV
- Volatility sensors now expose CV in attributes for transparency
Used as foundation for quality gates, adaptive smoothing, and period statistics.
Impact: Volatility sensors show numeric CV percentage alongside categorical level,
enabling users to see exact price variation.
Implemented configurable display format (mean/median/both) while always
calculating and exposing both price_mean and price_median attributes.
Core changes:
- utils/average.py: Refactored calculate_mean_median() to always return both
values, added comprehensive None handling (117 lines changed)
- sensor/attributes/helpers.py: Always include both attributes regardless of
user display preference (41 lines)
- sensor/core.py: Dynamic _unrecorded_attributes based on display setting
(55 lines), extracted helper methods to reduce complexity
- Updated all calculators (rolling_hour, trend, volatility, window_24h) to
use new always-both approach
Impact: Users can switch display format in UI without losing historical data.
Automation authors always have access to both statistical measures.
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.
Add user-configurable option to choose between median and arithmetic mean
as the displayed value for all 14 average price sensors, with the alternate
value exposed as attribute.
BREAKING CHANGE: Average sensor default changed from arithmetic mean to
median. Users who rely on arithmetic mean behavior may use the price_mean attribue now, or must manually reconfigure
via Settings → Devices & Services → Tibber Prices → Configure → General
Settings → "Average Sensor Display" → Select "Arithmetic Mean" to get this as sensor state.
Affected sensors (14 total):
- Daily averages: average_price_today, average_price_tomorrow
- 24h windows: trailing_price_average, leading_price_average
- Rolling hour: current_hour_average_price, next_hour_average_price
- Future forecasts: next_avg_3h, next_avg_6h, next_avg_9h, next_avg_12h
Implementation:
- All average calculators now return (mean, median) tuples
- User preference controls which value appears in sensor state
- Alternate value automatically added to attributes
- Period statistics (best_price/peak_price) extended with both values
Technical changes:
- New config option: CONF_AVERAGE_SENSOR_DISPLAY (default: "median")
- Calculator functions return tuples: (avg, median)
- Attribute builders: add_alternate_average_attribute() helper function
- Period statistics: price_avg → price_mean + price_median
- Translations: Updated all 5 languages (de, en, nb, nl, sv)
- Documentation: AGENTS.md, period-calculation.md, recorder-optimization.md
Migration path:
Users can switch back to arithmetic mean via:
Settings → Integrations → Tibber Prices → Configure
→ General Settings → "Average Sensor Display" → "Arithmetic Mean"
Impact: Median is more resistant to price spikes, providing more stable
automation triggers. Statistical analysis from coordinator still uses
arithmetic mean (e.g., trailing_avg_24h for rating calculations).
Co-developed-with: GitHub Copilot <copilot@github.com>
Implemented comprehensive entity lifecycle patterns following Home Assistant
best practices for proper state management and history tracking.
Changes:
- entity.py: Added available property to base class
- Returns False when coordinator has no data or last_update_success=False
- Prevents entities from showing stale data during errors
- Auth failures trigger reauth flow via ConfigEntryAuthFailed
- sensor/core.py: Added state restore and background task handling
- Changed inheritance: SensorEntity → RestoreSensor
- Restore native_value from SensorExtraStoredData in async_added_to_hass()
- Chart sensors restore response data from attributes
- Converted blocking service calls to background tasks using hass.async_create_task()
- Eliminates 194ms setup warning by making async_added_to_hass non-blocking
- binary_sensor/core.py: Added state restore and force_update
- Changed inheritance: BinarySensorEntity → RestoreEntity + BinarySensorEntity
- Restore is_on state in async_added_to_hass()
- Added available property override for connection sensor (always True)
- Added force_update property for connection sensor to track all state changes
- Other binary sensors use base available logic
- AGENTS.md: Documented entity lifecycle patterns in Common Pitfalls
- Added "Entity Lifecycle & State Management" section
- Documents available, state restore, and force_update patterns
- Explains why each pattern matters for proper HA integration
Impact: Entities no longer show stale data during errors, history has no gaps
after HA restart, connection state changes are properly tracked, and config
entry setup completes in <200ms (under HA threshold).
All patterns verified against HA developer documentation:
https://developers.home-assistant.io/docs/core/entity/
Implement _unrecorded_attributes in both sensor and binary_sensor
entities to prevent Home Assistant Recorder database bloat.
Excluded attributes (60-85% size reduction per state):
- Descriptions/help text (static, large strings)
- Large nested structures (periods, trend_attributes, chart data)
- Frequently changing diagnostics (icon_color, cache_age)
- Static/rarely changing config (currency, resolution)
- Temporary/time-bound data (next_api_poll, last_*)
- Redundant/derived data (price_spread, diff_%)
Kept for history analysis:
- timestamp (always first), all price values
- Period timing (start, end, duration_minutes)
- Price statistics (avg, min, max)
- Boolean status flags, relaxation_active
Impact: Reduces attribute size from ~3-8 KB to ~0.5-1.5 KB per state
change. Expected savings: ~1 GB per month for typical installation.
See: https://developers.home-assistant.io/docs/core/entity/#excluding-state-attributes-from-recorder-history
Implementation flaw discovered: gradient_stop calculated as
`(avg - min) / (max - min)` for combined data produces one value
applied to ALL series. Each series (VERY_CHEAP, NORMAL, VERY_EXPENSIVE)
has different min/max ranges, so the same gradient stop position
represents a different absolute price in each series.
Example failure case:
- VERY_CHEAP: 10-20 ct → 50% at 15 ct (below overall avg!)
- VERY_EXPENSIVE: 40-50 ct → 50% at 45 ct (above overall avg!)
Conclusion: Gradient shows middle of each series range, not average
price position.
Solution: Fixed 50% gradient purely for visual appeal. Semantic
information provided by:
- Series colors (CHEAP/NORMAL/EXPENSIVE)
- Grid lines (implicitly show average)
- Dynamic Y-axis bounds (optimal scaling via chart_metadata sensor)
Changes:
- sensor/chart_metadata.py: Remove gradient_stop extraction
- services/get_apexcharts_yaml.py: Fixed gradient at [50, 100]
- custom_translations/*.json: Remove gradient_stop references
Impact: Honest visualization with no false semantic signals. Feature
was never released, clean removal without migration.
Implemented new chart_metadata diagnostic sensor that provides essential
chart configuration values (yaxis_min, yaxis_max, gradient_stop) as
attributes, enabling dynamic chart configuration without requiring
async service calls in templates.
Sensor implementation:
- New chart_metadata.py module with metadata-only service calls
- Automatically calls get_chartdata with metadata="only" parameter
- Refreshes on coordinator updates (new price data or user data)
- State values: "pending", "ready", "error"
- Enabled by default (critical for chart features)
ApexCharts YAML generator integration:
- Checks for chart_metadata sensor availability before generation
- Uses template variables to read sensor attributes dynamically
- Fallback to fixed values (gradient_stop=50%) if sensor unavailable
- Creates separate notifications for two independent issues:
1. Chart metadata sensor disabled (reduced functionality warning)
2. Required custom cards missing (YAML won't work warning)
- Both notifications explain YAML generation context and provide
complete fix instructions with regeneration requirement
Configuration:
- Supports configuration.yaml: tibber_prices.chart_metadata_config
- Optional parameters: day, minor_currency, resolution
- Defaults to minor_currency=True for ApexCharts compatibility
Translation additions:
- Entity name and state translations (all 5 languages)
- Notification messages for sensor unavailable and missing cards
- best_price_period_name for tooltip formatter
Binary sensor improvements:
- tomorrow_data_available now enabled by default (critical for automations)
- data_lifecycle_status now enabled by default (critical for debugging)
Impact: Users get dynamic chart configuration with optimized Y-axis scaling
and gradient positioning without manual calculations. ApexCharts YAML
generation now provides clear, actionable notifications when issues occur,
ensuring users understand why functionality is limited and how to fix it.
Changed needs_tomorrow_data() to auto-calculate tomorrow date using
get_intervals_for_day_offsets([1]) helper instead of requiring explicit
tomorrow_date parameter.
Changes:
- coordinator/helpers.py: needs_tomorrow_data() signature simplified
* Uses get_intervals_for_day_offsets([1]) to detect tomorrow intervals
* No longer requires tomorrow_date parameter (calculated automatically)
* Consistent with all other data access patterns
- coordinator/data_fetching.py: Removed tomorrow_date calculation and passing
* Removed unused date import
* Simplified method call: needs_tomorrow_data() instead of needs_tomorrow_data(tomorrow_date)
- sensor/calculators/lifecycle.py: Updated calls to _needs_tomorrow_data()
* Removed tomorrow_date variable where it was only used for this call
* Combined nested if statements with 'and' operator
Impact: Cleaner API, fewer parameters to track, consistent with other
helper functions that auto-calculate dates based on current time.
Changed from centralized main+subentry coordinator pattern to independent
coordinators per home. Each config entry now manages its own home data
with its own API client and access token.
Architecture changes:
- API Client: async_get_price_info() changed from home_ids: set[str] to home_id: str
* Removed GraphQL alias pattern (home0, home1, ...)
* Single-home query structure without aliasing
* Simplified response parsing (viewer.home instead of viewer.home0)
- Coordinator: Removed main/subentry distinction
* Deleted is_main_entry() and _has_existing_main_coordinator()
* Each coordinator fetches its own data independently
* Removed _find_main_coordinator() and _get_configured_home_ids()
* Simplified _async_update_data() - no subentry logic
* Added _home_id instance variable from config_entry.data
- __init__.py: New _get_access_token() helper
* Handles token retrieval for both parent and subentries
* Subentries find parent entry to get shared access token
* Creates single API client instance per coordinator
- Data structures: Flat single-home format
* Old: {"homes": {home_id: {"price_info": [...]}}}
* New: {"home_id": str, "price_info": [...], "currency": str}
* Attribute name: "periods" → "pricePeriods" (consistent with priceInfo)
- helpers.py: Removed get_configured_home_ids() (no longer needed)
* parse_all_timestamps() updated for single-home structure
Impact: Each home operates independently with its own lifecycle tracking,
caching, and period calculations. Simpler architecture, easier debugging,
better isolation between homes.
- Introduced `get_intervals_for_day_offsets` helper to streamline access to price intervals for yesterday, today, and tomorrow.
- Updated various components to replace direct access to `priceInfo` with the new helper, ensuring a flat structure for price intervals.
- Adjusted calculations and data processing methods to accommodate the new data structure.
- Enhanced documentation to reflect changes in caching strategy and data structure.
Simplified _has_future_periods() to check for ANY future periods instead
of limiting to 6-hour window. This ensures icons show 'waiting' state
whenever periods are scheduled, not just within artificial time limit.
Also added pragmatic fallback in timing calculator _find_next_period():
when skip_current=True but only one future period exists, return it
anyway instead of showing 'unknown'. This prevents timing sensors from
showing unknown during active periods.
Changes:
- binary_sensor/definitions.py: Removed PERIOD_LOOKAHEAD_HOURS constant
- binary_sensor/core.py: Simplified _has_future_periods() logic
- sensor/calculators/timing.py: Added pragmatic fallback for single period
Impact: Better user experience - icons always show future periods, timing
sensors show values even during edge cases.
Removed custom lifecycle callback push-update mechanism after confirming
it was redundant with Home Assistant's built-in DataUpdateCoordinator
pattern.
Root cause analysis showed HA's async_update_listeners() is called
synchronously (no await) immediately after _async_update_data() returns,
making separate lifecycle callbacks unnecessary.
Changes:
- coordinator/core.py: Removed lifecycle callback methods and notifications
- sensor/core.py: Removed lifecycle callback registration and cleanup
- sensor/attributes/lifecycle.py: Removed next_tomorrow_check attribute
- sensor/calculators/lifecycle.py: Removed get_next_tomorrow_check_time()
Impact: Simplified coordinator pattern, no user-visible changes. Standard
HA coordinator mechanism provides same immediate update guarantee without
custom callback complexity.
Fixed multiple calculation issues with negative prices (Norway/Germany
renewable surplus scenarios):
Bug #6: Rating threshold validation with dead code
- Added threshold validation (low >= high) with warning
- Returns NORMAL as fallback for misconfigured thresholds
Bug #7: Min/Max functions returning 0.0 instead of None
- Changed default from 0.0 to None when window is empty
- Prevents misinterpretation (0.0 looks like price with negatives)
Bug #9: Period price diff percentage wrong sign with negative reference
- Use abs(ref_price) in percentage calculation
- Correct percentage direction for negative prices
Bug #10: Trend diff percentage wrong sign with negative current price
- Use abs(current_interval_price) in percentage calculation
- Correct trend direction when prices cross zero
Bug #11: later_half_diff calculation failed for negative prices
- Changed condition from `if current_interval_price > 0` to `!= 0`
- Use abs(current_interval_price) for percentage
Changes:
- utils/price.py: Add threshold validation, use abs() in percentages
- utils/average.py: Return None instead of 0.0 for empty windows
- period_statistics.py: Use abs() for reference prices
- trend.py: Use abs() for current prices, fix zero-check condition
- tests: 95+ new tests covering negative/zero/mixed price scenarios
Impact: All calculations work correctly with negative electricity prices.
Percentages show correct direction regardless of sign.
chart_data_export now registers lifecycle callback for immediate
updates when coordinator data changes ("fresh" lifecycle state).
Previously only updated via polling intervals.
Changes:
- Register callback in sensor constructor (when entity_key matches)
- Callback triggers async_write_ha_state() on "fresh" lifecycle
- 5 new tests covering callback registration and triggering
Impact: Chart data export updates immediately on API data arrival,
enabling real-time dashboard updates without polling delay.
Cache validity now checks _last_coordinator_update (within 30min)
instead of _api_calls_today counter. Fixes false "stale" status
when coordinator runs every 15min but cache validation was only
checking API call counter.
Bug #1: Cache validity shows "stale" at 05:57 AM
Bug #2: Cache age calculation incorrect after midnight turnover
Bug #3: get_cache_validity inconsistent with cache_age sensor
Changes:
- Coordinator: Use _last_coordinator_update for cache validation
- Lifecycle: Extract cache validation to dedicated helper function
- Tests: 7 new tests covering midnight scenarios and edge cases
Impact: Cache validity sensor now accurately reflects coordinator
activity, not just explicit API calls. Correctly handles midnight
turnover without false "stale" status.
Add comprehensive data_lifecycle_status sensor showing real-time cache
vs fresh API data status with 6 states and 13+ detailed attributes.
Key features:
- 6 lifecycle states: cached, fresh, refreshing, searching_tomorrow,
turnover_pending, error
- Push-update system for instant state changes (refreshing→fresh→error)
- Quarter-hour polling for turnover_pending detection at 23:45
- Accurate next_api_poll prediction using Timer #1 offset tracking
- Tomorrow prediction with actual timer schedule (not fixed 13:00)
- 13+ formatted attributes: cache_age, data_completeness, api_calls_today,
next_api_poll, etc.
Implementation:
- sensor/calculators/lifecycle.py: New calculator with state logic
- sensor/attributes/lifecycle.py: Attribute builders with formatting
- coordinator/core.py: Lifecycle tracking + callback system (+16 lines)
- sensor/core.py: Push callback registration (+3 lines)
- coordinator/constants.py: Added to TIME_SENSITIVE_ENTITY_KEYS
- Translations: All 5 languages (de, en, nb, nl, sv)
Timing optimization:
- Extended turnover warning: 5min → 15min (catches 23:45 quarter boundary)
- No minute-timer needed: quarter-hour updates + push = optimal
- Push-updates: <1sec latency for refreshing/fresh/error states
- Timer offset tracking: Accurate tomorrow predictions
Removed obsolete sensors:
- data_timestamp (replaced by lifecycle attributes)
- price_forecast (never implemented, removed from definitions)
Impact: Users can monitor data freshness, API call patterns, cache age,
and understand integration behavior. Perfect for troubleshooting and
visibility into when data updates occur.
Moved Chart Data Export sensor configuration from config flow textarea
to configuration.yaml for better maintainability and consistency with
Home Assistant standards.
Changes:
- __init__.py: Added async_setup() with CONFIG_SCHEMA for tibber_prices.chart_export
- const.py: Added DATA_CHART_CONFIG constant for hass.data storage
- options_flow.py: Simplified chart_data_export step to info-only page
- schemas.py: get_chart_data_export_schema() returns empty schema (no input fields)
- sensor/chart_data.py: Reads config from hass.data instead of config_entry.options
- All 5 translation files: Updated chart_data_export description with:
- Clear heading: "📊 Chart Data Export Sensor"
- Intro line explaining sensor purpose
- Legacy warning (⚠️) recommending service use
- Two valid use cases (✅): attribute-only tools, auto-updating data
- One discouraged use case (❌): automations should use service directly
- 3-step activation instructions
- YAML configuration example with all parameters
- Correct default behavior: today+tomorrow, 15-minute intervals, prices only
Impact: Users configure chart export in configuration.yaml instead of UI.
Sensor remains disabled by default (diagnostic sensor). Config flow shows
prominent info page guiding users toward service usage while keeping
sensor available for legacy dashboard tools that only read attributes.
Introduce TimeService as single source of truth for all datetime operations,
replacing direct dt_util calls throughout the codebase. This establishes
consistent time context across update cycles and enables future time-travel
testing capability.
Core changes:
- NEW: coordinator/time_service.py with timezone-aware datetime API
- Coordinator now creates TimeService per update cycle, passes to calculators
- Timer callbacks (#2, #3) inject TimeService into entity update flow
- All sensor calculators receive TimeService via coordinator reference
- Attribute builders accept time parameter for timestamp calculations
Key patterns replaced:
- dt_util.now() → time.now() (single reference time per cycle)
- dt_util.parse_datetime() + as_local() → time.get_interval_time()
- Manual interval arithmetic → time.get_interval_offset_time()
- Manual day boundaries → time.get_day_boundaries()
- round_to_nearest_quarter_hour() → time.round_to_nearest_quarter()
Import cleanup:
- Removed dt_util imports from ~30 files (calculators, attributes, utils)
- Restricted dt_util to 3 modules: time_service.py (operations), api/client.py
(rate limiting), entity_utils/icons.py (cosmetic updates)
- datetime/timedelta only for TYPE_CHECKING (type hints) or duration arithmetic
Interval resolution abstraction:
- Removed hardcoded MINUTES_PER_INTERVAL constant from 10+ files
- New methods: time.minutes_to_intervals(), time.get_interval_duration()
- Supports future 60-minute resolution (legacy data) via TimeService config
Timezone correctness:
- API timestamps (startsAt) already localized by data transformation
- TimeService operations preserve HA user timezone throughout
- DST transitions handled via get_expected_intervals_for_day() (future use)
Timestamp ordering preserved:
- Attribute builders generate default timestamp (rounded quarter)
- Sensors override when needed (next interval, daily midnight, etc.)
- Platform ensures timestamp stays FIRST in attribute dict
Timer integration:
- Timer #2 (quarter-hour): Creates TimeService, calls _handle_time_sensitive_update(time)
- Timer #3 (30-second): Creates TimeService, calls _handle_minute_update(time)
- Consistent time reference for all entities in same update batch
Time-travel readiness:
- TimeService.with_reference_time() enables time injection (not yet used)
- All calculations use time.now() → easy to simulate past/future states
- Foundation for debugging period calculations with historical data
Impact: Eliminates timestamp drift within update cycles (previously 60+ independent
dt_util.now() calls could differ by milliseconds). Establishes architecture for
time-based testing and debugging features.
Created entity_utils/helpers.py with platform-agnostic utility functions:
- get_price_value(): Price unit conversion (major/minor currency)
- translate_level(): Price level translation
- translate_rating_level(): Rating level translation
- find_rolling_hour_center_index(): Rolling hour window calculations
These functions moved from sensor/helpers.py as they are used by both
sensor and binary_sensor platforms. Remaining sensor/helpers.py now
contains only sensor-specific helpers (aggregate_price_data, etc.).
Updated imports:
- sensor/core.py: Import from entity_utils instead of sensor.helpers
- entity_utils/icons.py: Fixed find_rolling_hour_center_index import
- binary_sensor platforms: Can now use shared helpers
Added clear docstrings explaining:
- entity_utils/helpers.py: Platform-agnostic utilities
- sensor/helpers.py: Sensor-specific aggregation functions
Impact: Better code reuse, clearer responsibility boundaries between
platform-specific and shared utilities.
This commit completes multiple refactoring efforts and documentation improvements:
Code Structure Changes:
- Move round_to_nearest_quarter_hour() from sensor/helpers.py to average_utils.py
- Resolve circular import between price_utils.py and sensor/helpers.py
- Split api.py into api/ package (client.py, queries.py, exceptions.py, helpers.py)
- Split coordinator.py into coordinator/ package (core.py, cache.py, listeners.py, etc.)
- Move period_utils/ to coordinator/period_handlers/ for better organization
- All lint checks passing (no PLC0415 local import warnings)
Documentation Additions:
- Add docs/development/architecture.md with Mermaid diagrams (end-to-end flow, cache coordination)
- Add docs/development/timer-architecture.md (comprehensive 3-timer system documentation)
- Add docs/development/caching-strategy.md (4-layer cache system with invalidation logic)
- Update docs/development/README.md with cross-references
- Update AGENTS.md with new module structure and patterns
Smart Boundary Tolerance:
- Implement ±2 second tolerance for quarter-hour rounding
- Prevents premature interval switching during HA restarts (14:59:30 stays at 14:45)
- Enables boundary snapping for timer jitter (14:59:58 → 15:00)
Atomic Midnight Coordination:
- Add _check_midnight_turnover_needed() for race-free midnight handling
- Coordinate Timer #1 (HA DataUpdateCoordinator) with Timer #2 (quarter-hour refresh)
- Whoever runs first performs turnover, other skips gracefully
Timer Optimization:
- Change timer scheduling from second=1 to second=0 (absolute-time scheduling)
- Document load distribution rationale (unsynchronized API polling prevents thundering herd)
- Comprehensive explanation of 3 independent timers and their coordination
Impact: Cleaner code structure with resolved circular dependencies, comprehensive
documentation of timer and caching systems, and improved reliability during
boundary conditions and midnight turnovers. All changes are developer-facing
improvements with no user-visible behavior changes.
Migrated chart_data_export from binary_sensor to sensor to enable
compatibility with dashboard integrations (ApexCharts, etc.) that
require sensor entities for data selection.
Changes:
- Moved chart_data_export from binary_sensor/ to sensor/ platform
- Changed from boolean state (ON/OFF) to ENUM states ("pending", "ready", "error")
- Maintained all functionality: service call, attribute structure, caching
- Updated translations in all 5 languages (de, en, nb, nl, sv)
- Updated user documentation (sensors.md, services.md)
- Removed all chart_data_export code from binary_sensor platform
Technical details:
- State: "pending" (before first call), "ready" (data available), "error" (service failed)
- Attributes: timestamp + error (metadata) → descriptions → service response data
- Cache (_chart_data_response) bridges async service call and sync property access
- Service call: Triggered on async_added_to_hass() and async_update()
Impact: Dashboard integrations can now select chart_data_export sensor
in their entity pickers. No breaking changes for existing users - entity ID
changes from binary_sensor.* to sensor.*, but functionality identical.
Enhanced current_price_trend and next_price_trend_change sensors with
consistent temporal information and fixed trend calculation logic.
Changes:
- Fixed trend calculation order: Calculate final trend state (momentum +
future outlook) BEFORE scanning for next change, ensuring consistency
between current_price_trend state and next_price_trend_change from_direction
- Added TIME_SENSITIVE_ENTITY_KEYS registration for both trend sensors
to enable automatic 15-minute boundary updates (Timer #2)
- Removed redundant timestamp field from _trend_change_attributes (was
duplicate of sensor state)
- Added timestamp attribute (current interval) to both sensors as first
attribute for temporal reference
- Implemented _find_trend_start_time() to scan backward and determine
when current trend began
- Added trend_duration_minutes to current_price_trend showing how long
current trend has been active
- Added from_direction to current_price_trend showing previous trend
state (enables detection of valleys/plateaus)
- Added minutes_until_change to next_price_trend_change showing time
until trend changes
- Removed redundant attributes: valid_until, duration_hours,
duration_minutes from current_price_trend (can be derived from
next_price_trend_change sensor)
- Removed redundant next_direction from current_price_trend (available
in next_price_trend_change sensor)
current_price_trend attributes:
- timestamp: Current interval (calculation basis)
- from_direction: Previous trend state (e.g., "stable" → "falling" = starting decline)
- trend_duration_minutes: How long current trend has been active
next_price_trend_change attributes:
- timestamp: Current interval (calculation basis)
- from_direction: Current trend state (should match current_price_trend state)
- direction: Target trend state
- minutes_until_change: Time until change occurs
- current_price_now, price_at_change, avg_after_change, trend_diff_%
Impact: Users can now detect important transitions (valleys: falling→stable,
plateaus: rising→stable) and understand trend context. Both sensors update
automatically every 15 minutes with consistent information.
API Client:
- Changed async_get_price_info() to accept home_ids parameter
- Implemented _get_price_info_for_specific_homes() using GraphQL aliases
(home0: home(id: "abc") { ... }) for efficient multi-home queries
- Extended async_get_viewer_details() with comprehensive home metadata
(owner, address, meteringPointData, subscription, features)
- Removed deprecated async_get_data() method (combined query no longer needed)
- Updated _is_data_empty() to handle aliased response structure
Coordinator:
- Added _get_configured_home_ids() to collect all active config entries
- Modified _fetch_all_homes_data() to only query configured homes
- Added refresh_user_data() forcing user data refresh (bypasses cache)
- Improved get_user_profile() with detailed user info (name, login, accountType)
- Fixed get_user_homes() to extract from viewer object
Binary Sensors:
- Added has_ventilation_system sensor (home metadata)
- Added realtime_consumption_enabled sensor (features check)
- Refactored state getter mapping to dictionary pattern
Diagnostic Sensors (12 new):
- Home metadata: home_type, home_size, main_fuse_size, number_of_residents,
primary_heating_source
- Metering point: grid_company, grid_area_code, price_area_code,
consumption_ean, production_ean, energy_tax_type, vat_type,
estimated_annual_consumption
- Subscription: subscription_status
- Added available property override to hide diagnostic sensors with no data
Config Flow:
- Fixed subentry flow to exclude parent home_id from available homes
- Added debug logging for home title generation
Entity:
- Made attribution translatable (get_translation("attribution"))
- Removed hardcoded user name suffix from subentry device names
Impact: Enables multi-home setups with dedicated subentries. Each home gets
its own set of sensors and only configured homes are queried (reduces API
load). New diagnostic sensors provide comprehensive home metadata from Tibber
API. Users can track ventilation systems, heating types, metering point info,
and subscription status.
Adjusted entity_registry_enabled_default flags to reduce initial entity
count while keeping most useful sensors active by default.
Changes:
- Disabled rating sensors (current/next interval, hourly, daily) - Level
sensors provide better granularity (5 levels vs 3) for automations
- Disabled leading 24h window sensors (avg/min/max) - Advanced use case,
overlaps with tomorrow statistics
- Disabled additional volatility sensors (tomorrow, next_24h,
today_tomorrow) - Today's volatility sufficient for typical use cases
Rationale:
- Price level sensors (5 states: very_cheap → very_expensive) are more
commonly used than rating sensors (3 states: low → high)
- Leading 24h windows overlap with tomorrow daily statistics which have
clearer boundaries
- Single volatility indicator (today) covers most automation needs
Impact: Reduces default active entities from ~65 to ~50 while maintaining
all essential functionality. Advanced users can enable additional sensors
as needed. Improves initial setup experience by focusing on most relevant
sensors.
Added dedicated sensor for Home Assistant's Energy Dashboard integration and
new sensors to track total period duration for best/peak price periods.
New Sensors:
- current_interval_price_major: Shows price in major currency (EUR/kWh, NOK/kWh)
instead of minor units (ct/kWh, øre/kWh) for Energy Dashboard compatibility
- best_price_period_duration: Total length of current/next best price period
- peak_price_period_duration: Total length of current/next peak price period
Changes:
- sensor/definitions.py: Added 3 new sensor definitions with proper device_class,
state_class, and suggested_display_precision
- sensor/core.py: Extended native_unit_of_measurement property to return major
currency unit for Energy Dashboard sensor while keeping minor units for others
- sensor/core.py: Added _calc_period_duration() method to calculate period lengths
- sensor/core.py: Added handler mappings for new duration sensors
- const.py: Imported format_price_unit_major() for currency formatting
- translations/*.json: Added entity names for all 5 languages (de, en, nb, nl, sv)
- custom_translations/*.json: Added descriptions, long_descriptions, and usage_tips
for all new sensors in all 5 languages
Technical Details:
- Energy Dashboard sensor uses 4 decimal precision (0.2534 EUR/kWh) vs 2 decimals
for regular price sensors (25.34 ct/kWh)
- Duration sensors return minutes (UnitOfTime.MINUTES) with 0 decimal precision
- Duration sensors disabled by default (less commonly needed than end time)
- All MONETARY sensors now have explicit state_class=SensorStateClass.TOTAL
- All ENUM/TIMESTAMP sensors have explicit state_class=None for clarity
Impact: Users can now add electricity prices to Energy Dashboard for automatic
cost calculation. Duration sensors help users plan appliance usage by showing
how long cheap/expensive periods last. All price statistics now properly tracked
by Home Assistant's recorder.
Added 10 new timing sensors (5 for best_price, 5 for peak_price) to track
period timing and progress:
Timestamp sensors (quarter-hour updates):
- best_price_end_time / peak_price_end_time
Shows when current/next period ends (always useful reference time)
- best_price_next_start_time / peak_price_next_start_time
Shows when next period starts (even during active periods)
Countdown sensors (minute updates):
- best_price_remaining_minutes / peak_price_remaining_minutes
Minutes left in current period (0 when inactive)
- best_price_next_in_minutes / peak_price_next_in_minutes
Minutes until next period starts
- best_price_progress / peak_price_progress
Progress percentage through current period (0-100%)
Smart fallback behavior:
- Sensors always show useful values (no 'Unknown' during normal operation)
- Timestamp sensors show current OR next period end/start times
- Countdown sensors return 0 when no period is active
- Grace period: Progress stays at 100% for 60 seconds after period ends
Dynamic visual feedback:
- Progress icons differentiate 3 states at 0%:
* No data: mdi:help-circle-outline (gray)
* Waiting for next period: mdi:timer-pause-outline
* Period just started: mdi:circle-outline
- Progress 1-99%: mdi:circle-slice-1 to mdi:circle-slice-8 (pie chart)
- Timer icons based on urgency (alert/timer/timer-sand/timer-outline)
- Dynamic colors: green (best_price), orange/red (peak_price), gray (disabled)
- icon_color attribute for UI styling
Implementation details:
- Dual update mechanism: quarter-hour (timestamps) + minute (countdowns)
- Period state callbacks: Check if period is currently active
- IconContext dataclass: Reduced function parameters from 6 to 3
- Unit constants: UnitOfTime.MINUTES, PERCENTAGE from homeassistant.const
- Complete translations for 5 languages (de, en, nb, nl, sv)
Impact: Users can now build sophisticated automations based on period timing
('start dishwasher if remaining_minutes > 60'), display countdowns in
dashboards, and get clear visual feedback about period states. All sensors
provide meaningful values at all times, making automation logic simpler.
Added timestamp attributes to all sensors and enhanced the dynamic icon
system for comprehensive price sensor coverage with rolling hour support.
TIMESTAMP ATTRIBUTES:
Core Changes:
- sensor/attributes.py:
* Enhanced add_average_price_attributes() to track extreme intervals
for min/max sensors and add appropriate timestamps
* Added _update_extreme_interval() helper to reduce complexity
* Extended add_volatility_type_attributes() with timestamp logic for
all 4 volatility types (today/tomorrow/today_tomorrow/next_24h)
* Fixed current_interval_price timestamp assignment (use interval_data)
Timestamp Logic:
- Interval-based sensors: Use startsAt of specific 15-minute interval
- Min/Max sensors: Use startsAt of interval with extreme price
- Average sensors: Use startsAt of first interval in window
- Volatility sensors: Use midnight (00:00) for calendar day sensors,
current time for rolling 24h window
- Daily sensors: Already used fallback to midnight (verified)
ICON SYSTEM ENHANCEMENTS:
Major Extensions:
- entity_utils/icons.py:
* Created get_rolling_hour_price_level_for_icon() implementing
5-interval window aggregation matching sensor calculation logic
* Extended get_price_sensor_icon() coverage from 1 to 4 sensors:
- current_interval_price (existing)
- next_interval_price (NEW - dynamic instead of static)
- current_hour_average_price (NEW - uses rolling hour aggregation)
- next_hour_average_price (NEW - uses rolling hour aggregation)
* Added imports for aggregate_level_data and find_rolling_hour_center_index
Documentation:
- sensor/definitions.py:
* Updated 30+ sensor descriptions with detailed icon behavior comments
* Changed next_interval_price from static to dynamic icon
* Documented dynamic vs static icons for all sensor types
* Added clear icon mapping source documentation
SENSOR KEY RENAMING:
Renamed for clarity (current_hour_average → current_hour_average_price):
- sensor/core.py: Updated value getters and cached data lookup
- sensor/definitions.py: Updated entity descriptions
- sensor/attributes.py: Updated key references in attribute builders
- coordinator.py: Updated TIME_SENSITIVE_ENTITY_KEYS set
- const.py: Updated comment documentation
Translation Updates:
- custom_translations/*.json (5 files): Updated sensor keys
- translations/*.json (5 files): Updated sensor keys
Impact:
- All sensors now have timestamp attribute showing applicable time/interval
- Icon system provides richer visual feedback for more sensor types
- Consistent sensor naming improves code readability
- Users get temporal context for all sensor values
- Dynamic icons adapt to price conditions across more sensors
Added 6 new sensors for yesterday/today/tomorrow aggregated price
levels and ratings, following the same calculation logic as existing
current/next interval sensors.
New sensors:
- yesterday_price_level, today_price_level, tomorrow_price_level
- yesterday_price_rating, today_price_rating, tomorrow_price_rating
Implementation details:
- Added DAILY_LEVEL_SENSORS and DAILY_RATING_SENSORS in sensor/definitions.py
- Implemented _get_daily_aggregated_value() in sensor/core.py using
existing aggregate_level_data() and aggregate_rating_data() helpers
- Extended icon support in entity_utils/icons.py for dynamic icons
- Added icon_color attributes in sensor/attributes.py with helper
functions _get_day_key_from_sensor_key() and _add_fallback_timestamp()
- Complete translations in all 5 languages (de, en, nb, nl, sv):
* Standard translations: sensor names
* Custom translations: description, long_description, usage_tips
Impact: Users can now see aggregated daily price levels and ratings
for yesterday, today, and tomorrow at a glance, making it easier to
compare overall price situations across days and plan energy consumption
accordingly. Sensors use same aggregation logic as hourly sensors for
consistency.