mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-03-29 21:03:40 +00:00
fix
This commit is contained in:
parent
2ef3217518
commit
b23697036a
2 changed files with 257 additions and 3 deletions
|
|
@ -10,16 +10,30 @@ import voluptuous as vol
|
|||
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, SupportsResponse, callback
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers.entity_registry import async_get as async_get_entity_registry
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
PRICE_LEVEL_CHEAP,
|
||||
PRICE_LEVEL_EXPENSIVE,
|
||||
PRICE_LEVEL_NORMAL,
|
||||
PRICE_LEVEL_VERY_CHEAP,
|
||||
PRICE_LEVEL_VERY_EXPENSIVE,
|
||||
PRICE_RATING_HIGH,
|
||||
PRICE_RATING_LOW,
|
||||
PRICE_RATING_NORMAL,
|
||||
get_price_level_translation,
|
||||
)
|
||||
|
||||
PRICE_SERVICE_NAME = "get_price"
|
||||
APEXCHARTS_DATA_SERVICE_NAME = "get_apexcharts_data"
|
||||
APEXCHARTS_YAML_SERVICE_NAME = "get_apexcharts_yaml"
|
||||
ATTR_DAY: Final = "day"
|
||||
ATTR_ENTRY_ID: Final = "entry_id"
|
||||
ATTR_TIME: Final = "time"
|
||||
|
||||
SERVICE_SCHEMA: Final = vol.Schema(
|
||||
PRICE_SERVICE_SCHEMA: Final = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_ENTRY_ID): str,
|
||||
vol.Optional(ATTR_DAY): vol.In(["yesterday", "today", "tomorrow"]),
|
||||
|
|
@ -27,6 +41,33 @@ SERVICE_SCHEMA: Final = vol.Schema(
|
|||
}
|
||||
)
|
||||
|
||||
APEXCHARTS_DATA_SERVICE_SCHEMA: Final = vol.Schema(
|
||||
{
|
||||
vol.Required("entity_id"): str,
|
||||
vol.Required("day"): vol.In(["yesterday", "today", "tomorrow"]),
|
||||
vol.Required("level_type"): vol.In(["level", "rating_level"]),
|
||||
vol.Required("level_key"): vol.In(
|
||||
[
|
||||
PRICE_LEVEL_CHEAP,
|
||||
PRICE_LEVEL_EXPENSIVE,
|
||||
PRICE_LEVEL_NORMAL,
|
||||
PRICE_LEVEL_VERY_CHEAP,
|
||||
PRICE_LEVEL_VERY_EXPENSIVE,
|
||||
PRICE_RATING_HIGH,
|
||||
PRICE_RATING_LOW,
|
||||
PRICE_RATING_NORMAL,
|
||||
]
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
APEXCHARTS_SERVICE_SCHEMA: Final = vol.Schema(
|
||||
{
|
||||
vol.Required("entity_id"): str,
|
||||
vol.Optional("day", default="today"): vol.In(["yesterday", "today", "tomorrow"]),
|
||||
}
|
||||
)
|
||||
|
||||
# region Top-level functions (ordered by call hierarchy)
|
||||
|
||||
# --- Entry point: Service handler ---
|
||||
|
|
@ -108,6 +149,122 @@ async def _get_price(call: ServiceCall) -> dict[str, Any]:
|
|||
return _build_price_response(response_ctx)
|
||||
|
||||
|
||||
async def _get_entry_id_from_entity_id(hass: HomeAssistant, entity_id: str) -> str | None:
|
||||
"""Return the config entry_id for a given entity_id."""
|
||||
entity_registry = async_get_entity_registry(hass)
|
||||
entry = entity_registry.async_get(entity_id)
|
||||
if entry is not None:
|
||||
return entry.config_entry_id
|
||||
return None
|
||||
|
||||
|
||||
async def _get_apexcharts_data(call: ServiceCall) -> dict[str, Any]:
|
||||
"""Return points for ApexCharts for a single level type (e.g., LOW, NORMAL, HIGH, etc)."""
|
||||
entity_id = call.data.get("entity_id", "sensor.tibber_price_today")
|
||||
day = call.data.get("day", "today")
|
||||
level_type = call.data.get("level_type", "rating_level")
|
||||
level_key = call.data.get("level_key")
|
||||
hass = call.hass
|
||||
entry_id = await _get_entry_id_from_entity_id(hass, entity_id)
|
||||
if not entry_id:
|
||||
raise ServiceValidationError(translation_domain=DOMAIN, translation_key="invalid_entity_id")
|
||||
entry, coordinator, data = _get_entry_and_data(hass, entry_id)
|
||||
points = []
|
||||
if level_type == "rating_level":
|
||||
entries = coordinator.data.get("priceRating", {}).get("hourly", [])
|
||||
price_info = coordinator.data.get("priceInfo", {})
|
||||
if day == "today":
|
||||
prefixes = _get_day_prefixes(price_info.get("today", []))
|
||||
if not prefixes:
|
||||
return {"points": []}
|
||||
entries = [e for e in entries if e.get("time", e.get("startsAt", "")).startswith(prefixes[0])]
|
||||
elif day == "tomorrow":
|
||||
prefixes = _get_day_prefixes(price_info.get("tomorrow", []))
|
||||
if not prefixes:
|
||||
return {"points": []}
|
||||
entries = [e for e in entries if e.get("time", e.get("startsAt", "")).startswith(prefixes[0])]
|
||||
elif day == "yesterday":
|
||||
prefixes = _get_day_prefixes(price_info.get("yesterday", []))
|
||||
if not prefixes:
|
||||
return {"points": []}
|
||||
entries = [e for e in entries if e.get("time", e.get("startsAt", "")).startswith(prefixes[0])]
|
||||
else:
|
||||
entries = coordinator.data.get("priceInfo", {}).get(day, [])
|
||||
if not entries:
|
||||
return {"points": []}
|
||||
for i in range(len(entries) - 1):
|
||||
p = entries[i]
|
||||
if p.get("level") != level_key:
|
||||
continue
|
||||
points.append([p.get("time") or p.get("startsAt"), round((p.get("total") or 0) * 100, 2)])
|
||||
if points:
|
||||
points.append([points[-1][0], None])
|
||||
return {"points": points}
|
||||
|
||||
|
||||
async def _get_apexcharts_yaml(call: ServiceCall) -> dict[str, Any]:
|
||||
"""Return a YAML snippet for an ApexCharts card using the get_apexcharts_data service for each level."""
|
||||
entity_id = call.data.get("entity_id", "sensor.tibber_price_today")
|
||||
day = call.data.get("day", "today")
|
||||
level_type = call.data.get("level_type", "rating_level")
|
||||
if level_type == "rating_level":
|
||||
series_levels = [
|
||||
(PRICE_RATING_LOW, "#2ecc71"),
|
||||
(PRICE_RATING_NORMAL, "#f1c40f"),
|
||||
(PRICE_RATING_HIGH, "#e74c3c"),
|
||||
]
|
||||
else:
|
||||
series_levels = [
|
||||
(PRICE_LEVEL_VERY_CHEAP, "#2ecc71"),
|
||||
(PRICE_LEVEL_CHEAP, "#27ae60"),
|
||||
(PRICE_LEVEL_NORMAL, "#f1c40f"),
|
||||
(PRICE_LEVEL_EXPENSIVE, "#e67e22"),
|
||||
(PRICE_LEVEL_VERY_EXPENSIVE, "#e74c3c"),
|
||||
]
|
||||
series = []
|
||||
for level_key, color in series_levels:
|
||||
name = get_price_level_translation(level_key, "en") or level_key
|
||||
data_generator = (
|
||||
f"const data = await hass.callService('tibber_prices', 'get_apexcharts_data', "
|
||||
f"{{ entity_id: '{entity_id}', day: '{day}', level_type: '{level_type}', level_key: '{level_key}' }});\n"
|
||||
f"return data.points;"
|
||||
)
|
||||
series.append(
|
||||
{
|
||||
"entity": entity_id,
|
||||
"name": name,
|
||||
"type": "area",
|
||||
"color": color,
|
||||
"yaxis_id": "price",
|
||||
"show": {"extremas": level_key != "NORMAL"},
|
||||
"data_generator": data_generator,
|
||||
}
|
||||
)
|
||||
title = "Preisphasen Tagesverlauf" if level_type == "rating" else "Preisniveau"
|
||||
return {
|
||||
"type": "custom:apexcharts-card",
|
||||
"update_interval": "5m",
|
||||
"span": {"start": "day"},
|
||||
"header": {
|
||||
"show": True,
|
||||
"title": title,
|
||||
"show_states": False,
|
||||
},
|
||||
"apex_config": {
|
||||
"stroke": {"curve": "stepline"},
|
||||
"fill": {"opacity": 0.4},
|
||||
"tooltip": {"x": {"format": "HH:mm"}},
|
||||
"legend": {"show": True},
|
||||
},
|
||||
"yaxis": [
|
||||
{"id": "price", "decimals": 0, "min": 0},
|
||||
],
|
||||
"now": {"show": True, "color": "#8e24aa", "label": "🕒 LIVE"},
|
||||
"all_series_config": {"stroke_width": 1, "show": {"legend_value": False}},
|
||||
"series": series,
|
||||
}
|
||||
|
||||
|
||||
# --- Direct helpers (called by service handler or each other) ---
|
||||
|
||||
|
||||
|
|
@ -232,6 +389,8 @@ def _merge_priceinfo_and_pricerating(price_info: list[dict], price_rating: list[
|
|||
continue
|
||||
if k == "difference":
|
||||
merged_interval["rating_difference_%"] = v
|
||||
elif k == "rating":
|
||||
merged_interval["rating"] = v
|
||||
else:
|
||||
merged_interval[f"rating_{k}"] = v
|
||||
merged.append(merged_interval)
|
||||
|
|
@ -536,7 +695,21 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
|||
DOMAIN,
|
||||
PRICE_SERVICE_NAME,
|
||||
_get_price,
|
||||
schema=SERVICE_SCHEMA,
|
||||
schema=PRICE_SERVICE_SCHEMA,
|
||||
supports_response=SupportsResponse.ONLY,
|
||||
)
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
APEXCHARTS_DATA_SERVICE_NAME,
|
||||
_get_apexcharts_data,
|
||||
schema=APEXCHARTS_DATA_SERVICE_SCHEMA,
|
||||
supports_response=SupportsResponse.ONLY,
|
||||
)
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
APEXCHARTS_YAML_SERVICE_NAME,
|
||||
_get_apexcharts_yaml,
|
||||
schema=APEXCHARTS_SERVICE_SCHEMA,
|
||||
supports_response=SupportsResponse.ONLY,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -30,3 +30,84 @@ get_price:
|
|||
required: false
|
||||
selector:
|
||||
time:
|
||||
get_apexcharts_data:
|
||||
name: Get ApexCharts Data
|
||||
description: >-
|
||||
Returns data for an ApexCharts card visualizing Tibber Prices for the selected day.
|
||||
fields:
|
||||
entity_id:
|
||||
name: Entity ID
|
||||
description: The entity_id for the Tibber price sensor to determine the config entry ID.
|
||||
required: true
|
||||
example: sensor.tibber_price_today
|
||||
selector:
|
||||
entity:
|
||||
domain: sensor
|
||||
integration: tibber_prices
|
||||
day:
|
||||
name: Day
|
||||
description: Which day to visualize (yesterday, today, or tomorrow).
|
||||
required: true
|
||||
default: today
|
||||
example: today
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- yesterday
|
||||
- today
|
||||
- tomorrow
|
||||
level_type:
|
||||
name: Level Type
|
||||
description: >-
|
||||
Select what data to return: 'rating_level' (the current official Tibber integration price level: LOW/NORMAL/HIGH) or 'level' (the original Tibber API price level: VERY_CHEAP, CHEAP, NORMAL, EXPENSIVE, VERY_EXPENSIVE).
|
||||
required: true
|
||||
default: rating_level
|
||||
example: rating_level
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- level
|
||||
- rating_level
|
||||
level_key:
|
||||
name: Level Key
|
||||
description: >-
|
||||
Select the key to use for the level type: For 'rating_level', use LOW/NORMAL/HIGH. For 'level', use VERY_CHEAP/CHEAP/LOW/NORMAL/HIGH/EXPENSIVE/VERY_EXPENSIVE.
|
||||
required: true
|
||||
default: NORMAL
|
||||
example: NORMAL
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- VERY_CHEAP
|
||||
- CHEAP
|
||||
- LOW
|
||||
- NORMAL
|
||||
- HIGH
|
||||
- EXPENSIVE
|
||||
- VERY_EXPENSIVE
|
||||
get_apexcharts_yaml:
|
||||
name: Get ApexCharts Card YAML
|
||||
description: >-
|
||||
Returns a ready-to-copy YAML snippet for an ApexCharts card visualizing Tibber Prices for the selected day. Use this to easily add a pre-configured chart to your dashboard. The YAML will use the get_price service for data.
|
||||
fields:
|
||||
entity_id:
|
||||
name: Entity ID
|
||||
description: The entity_id for the Tibber price sensor (used for context, but the chart fetches data via the service).
|
||||
required: true
|
||||
example: sensor.tibber_price_today
|
||||
selector:
|
||||
entity:
|
||||
domain: sensor
|
||||
integration: tibber_prices
|
||||
day:
|
||||
name: Day
|
||||
description: Which day to visualize (yesterday, today, or tomorrow).
|
||||
required: false
|
||||
default: today
|
||||
example: today
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- yesterday
|
||||
- today
|
||||
- tomorrow
|
||||
|
|
|
|||
Loading…
Reference in a new issue