mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-03-29 21:03:40 +00:00
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.
149 lines
5 KiB
Python
149 lines
5 KiB
Python
"""Chart metadata export functionality for Tibber Prices sensors."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
from custom_components.tibber_prices.const import DATA_CHART_METADATA_CONFIG, DOMAIN
|
|
|
|
if TYPE_CHECKING:
|
|
from datetime import datetime
|
|
|
|
from custom_components.tibber_prices.coordinator import TibberPricesDataUpdateCoordinator
|
|
from custom_components.tibber_prices.data import TibberPricesConfigEntry
|
|
from homeassistant.core import HomeAssistant
|
|
|
|
|
|
async def call_chartdata_service_for_metadata_async(
|
|
hass: HomeAssistant,
|
|
coordinator: TibberPricesDataUpdateCoordinator,
|
|
config_entry: TibberPricesConfigEntry,
|
|
) -> tuple[dict | None, str | None]:
|
|
"""
|
|
Call get_chartdata service with configuration from configuration.yaml for metadata (async).
|
|
|
|
Returns:
|
|
Tuple of (response, error_message).
|
|
If successful: (response_dict, None)
|
|
If failed: (None, error_string)
|
|
|
|
"""
|
|
# Get configuration from hass.data (loaded from configuration.yaml)
|
|
domain_data = hass.data.get(DOMAIN, {})
|
|
chart_metadata_config = domain_data.get(DATA_CHART_METADATA_CONFIG, {})
|
|
|
|
# Use chart_metadata_config directly (already a dict from async_setup)
|
|
service_params = dict(chart_metadata_config) if chart_metadata_config else {}
|
|
|
|
# Add required entry_id parameter
|
|
service_params["entry_id"] = config_entry.entry_id
|
|
|
|
# Force metadata to "only" - this sensor ONLY provides metadata
|
|
service_params["metadata"] = "only"
|
|
|
|
# Default to minor_currency=True for ApexCharts compatibility (can be overridden in configuration.yaml)
|
|
if "minor_currency" not in service_params:
|
|
service_params["minor_currency"] = True
|
|
|
|
# Call get_chartdata service using official HA service system
|
|
try:
|
|
response = await hass.services.async_call(
|
|
DOMAIN,
|
|
"get_chartdata",
|
|
service_params,
|
|
blocking=True,
|
|
return_response=True,
|
|
)
|
|
except Exception as ex:
|
|
coordinator.logger.exception("Chart metadata service call failed")
|
|
return None, str(ex)
|
|
else:
|
|
return response, None
|
|
|
|
|
|
def get_chart_metadata_state(
|
|
chart_metadata_response: dict | None,
|
|
chart_metadata_error: str | None,
|
|
) -> str | None:
|
|
"""
|
|
Return state for chart_metadata sensor.
|
|
|
|
Args:
|
|
chart_metadata_response: Last service response (or None)
|
|
chart_metadata_error: Last error message (or None)
|
|
|
|
Returns:
|
|
"error" if error occurred
|
|
"ready" if metadata available
|
|
"pending" if no data yet
|
|
|
|
"""
|
|
if chart_metadata_error:
|
|
return "error"
|
|
if chart_metadata_response:
|
|
return "ready"
|
|
return "pending"
|
|
|
|
|
|
def build_chart_metadata_attributes(
|
|
chart_metadata_response: dict | None,
|
|
chart_metadata_last_update: datetime | None,
|
|
chart_metadata_error: str | None,
|
|
) -> dict[str, object] | None:
|
|
"""
|
|
Return chart metadata from last service call as attributes.
|
|
|
|
Attribute order: timestamp, error (if any), metadata fields (at the end).
|
|
|
|
Args:
|
|
chart_metadata_response: Last service response (should contain "metadata" key)
|
|
chart_metadata_last_update: Timestamp of last update
|
|
chart_metadata_error: Error message if service call failed
|
|
|
|
Returns:
|
|
Dict with timestamp, optional error, and metadata fields.
|
|
|
|
"""
|
|
# Build base attributes with timestamp FIRST
|
|
attributes: dict[str, object] = {
|
|
"timestamp": chart_metadata_last_update,
|
|
}
|
|
|
|
# Add error message if service call failed
|
|
if chart_metadata_error:
|
|
attributes["error"] = chart_metadata_error
|
|
|
|
if not chart_metadata_response:
|
|
# No data - only timestamp (and error if present)
|
|
return attributes
|
|
|
|
# Extract metadata from response (get_chartdata returns {"metadata": {...}})
|
|
metadata = chart_metadata_response.get("metadata", {})
|
|
|
|
# Extract the fields we care about for charts
|
|
# These are the universal chart metadata fields useful for any chart card
|
|
if metadata:
|
|
yaxis_suggested = metadata.get("yaxis_suggested", {})
|
|
price_stats = metadata.get("price_stats", {})
|
|
combined_stats = price_stats.get("combined", {})
|
|
|
|
# Add yaxis bounds (useful for all chart cards)
|
|
if "min" in yaxis_suggested:
|
|
attributes["yaxis_min"] = yaxis_suggested["min"]
|
|
if "max" in yaxis_suggested:
|
|
attributes["yaxis_max"] = yaxis_suggested["max"]
|
|
|
|
# Add gradient stop position (useful for gradient-based charts)
|
|
if "avg_position" in combined_stats:
|
|
avg_position = combined_stats["avg_position"]
|
|
attributes["gradient_stop"] = round(avg_position * 100)
|
|
|
|
# Add currency info (useful for labeling)
|
|
if "currency" in metadata:
|
|
attributes["currency"] = metadata["currency"]
|
|
|
|
# Add resolution info (interval duration in minutes)
|
|
if "resolution" in metadata:
|
|
attributes["resolution"] = metadata["resolution"]
|
|
|
|
return attributes
|