mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-03-30 05:13:40 +00:00
add friendly name
This commit is contained in:
parent
02a226819a
commit
94ef6ed4a6
4 changed files with 207 additions and 61 deletions
|
|
@ -2,7 +2,9 @@
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
from collections.abc import Sequence
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import aiofiles
|
import aiofiles
|
||||||
|
|
||||||
|
|
@ -114,6 +116,75 @@ async def async_load_translations(hass: HomeAssistant, language: str) -> dict:
|
||||||
return empty_cache
|
return empty_cache
|
||||||
|
|
||||||
|
|
||||||
|
async def async_get_translation(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
path: Sequence[str],
|
||||||
|
language: str = "en",
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Get a translation value by path asynchronously.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hass: HomeAssistant instance
|
||||||
|
path: A sequence of keys defining the path to the translation value
|
||||||
|
language: The language code (defaults to English)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The translation value if found, None otherwise
|
||||||
|
|
||||||
|
"""
|
||||||
|
translations = await async_load_translations(hass, language)
|
||||||
|
|
||||||
|
# Navigate to the requested path
|
||||||
|
current = translations
|
||||||
|
for key in path:
|
||||||
|
if not isinstance(current, dict) or key not in current:
|
||||||
|
return None
|
||||||
|
current = current[key]
|
||||||
|
|
||||||
|
return current
|
||||||
|
|
||||||
|
|
||||||
|
def get_translation(
|
||||||
|
path: Sequence[str],
|
||||||
|
language: str = "en",
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Get a translation value by path synchronously from the cache.
|
||||||
|
|
||||||
|
This function only accesses the cached translations to avoid blocking I/O.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path: A sequence of keys defining the path to the translation value
|
||||||
|
language: The language code (defaults to English)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The translation value if found in cache, None otherwise
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Only return from cache to avoid blocking I/O
|
||||||
|
if language not in _TRANSLATIONS_CACHE:
|
||||||
|
# Fall back to English if the requested language is not available
|
||||||
|
if language != "en" and "en" in _TRANSLATIONS_CACHE:
|
||||||
|
language = "en"
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Navigate to the requested path
|
||||||
|
current = _TRANSLATIONS_CACHE[language]
|
||||||
|
for key in path:
|
||||||
|
if not isinstance(current, dict):
|
||||||
|
return None
|
||||||
|
if key not in current:
|
||||||
|
# Log the missing key for debugging
|
||||||
|
LOGGER.debug("Translation key '%s' not found in path %s for language %s", key, path, language)
|
||||||
|
return None
|
||||||
|
current = current[key]
|
||||||
|
|
||||||
|
return current
|
||||||
|
|
||||||
|
|
||||||
|
# Convenience functions for backward compatibility and common usage patterns
|
||||||
async def async_get_entity_description(
|
async def async_get_entity_description(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entity_type: str,
|
entity_type: str,
|
||||||
|
|
@ -135,18 +206,13 @@ async def async_get_entity_description(
|
||||||
The requested field's value if found, None otherwise
|
The requested field's value if found, None otherwise
|
||||||
|
|
||||||
"""
|
"""
|
||||||
translations = await async_load_translations(hass, language)
|
entity_data = await async_get_translation(hass, [entity_type, entity_key], language)
|
||||||
|
|
||||||
# Check if entity exists in translations
|
# Handle the case where entity_data is a string (for description field)
|
||||||
if entity_type in translations and entity_key in translations[entity_type]:
|
|
||||||
# Get the entity data
|
|
||||||
entity_data = translations[entity_type][entity_key]
|
|
||||||
|
|
||||||
# If entity_data is a string, return it only for description field
|
|
||||||
if isinstance(entity_data, str) and field == "description":
|
if isinstance(entity_data, str) and field == "description":
|
||||||
return entity_data
|
return entity_data
|
||||||
|
|
||||||
# If entity_data is a dict, look for the requested field
|
# Handle the case where entity_data is a dict
|
||||||
if isinstance(entity_data, dict) and field in entity_data:
|
if isinstance(entity_data, dict) and field in entity_data:
|
||||||
return entity_data[field]
|
return entity_data[field]
|
||||||
|
|
||||||
|
|
@ -154,7 +220,10 @@ async def async_get_entity_description(
|
||||||
|
|
||||||
|
|
||||||
def get_entity_description(
|
def get_entity_description(
|
||||||
entity_type: str, entity_key: str, language: str = "en", field: str = "description"
|
entity_type: str,
|
||||||
|
entity_key: str,
|
||||||
|
language: str = "en",
|
||||||
|
field: str = "description",
|
||||||
) -> str | None:
|
) -> str | None:
|
||||||
"""
|
"""
|
||||||
Get entity description synchronously from the cache.
|
Get entity description synchronously from the cache.
|
||||||
|
|
@ -171,16 +240,54 @@ def get_entity_description(
|
||||||
The requested field's value if found in cache, None otherwise
|
The requested field's value if found in cache, None otherwise
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Only return from cache to avoid blocking I/O
|
entity_data = get_translation([entity_type, entity_key], language)
|
||||||
if language in _TRANSLATIONS_CACHE:
|
|
||||||
translations = _TRANSLATIONS_CACHE[language]
|
|
||||||
if entity_type in translations and entity_key in translations[entity_type]:
|
|
||||||
entity_data = translations[entity_type][entity_key]
|
|
||||||
|
|
||||||
|
# Handle the case where entity_data is a string (for description field)
|
||||||
if isinstance(entity_data, str) and field == "description":
|
if isinstance(entity_data, str) and field == "description":
|
||||||
return entity_data
|
return entity_data
|
||||||
|
|
||||||
|
# Handle the case where entity_data is a dict
|
||||||
if isinstance(entity_data, dict) and field in entity_data:
|
if isinstance(entity_data, dict) and field in entity_data:
|
||||||
return entity_data[field]
|
return entity_data[field]
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def async_get_price_level_translation(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
level: str,
|
||||||
|
language: str = "en",
|
||||||
|
) -> str | None:
|
||||||
|
"""
|
||||||
|
Get a localized translation for a price level asynchronously.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hass: HomeAssistant instance
|
||||||
|
level: The price level (e.g., VERY_CHEAP, NORMAL, etc.)
|
||||||
|
language: The language code (defaults to English)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The localized price level if found, None otherwise
|
||||||
|
|
||||||
|
"""
|
||||||
|
return await async_get_translation(hass, ["sensor", "price_level", "price_levels", level], language)
|
||||||
|
|
||||||
|
|
||||||
|
def get_price_level_translation(
|
||||||
|
level: str,
|
||||||
|
language: str = "en",
|
||||||
|
) -> str | None:
|
||||||
|
"""
|
||||||
|
Get a localized translation for a price level synchronously from the cache.
|
||||||
|
|
||||||
|
This function only accesses the cached translations to avoid blocking I/O.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
level: The price level (e.g., VERY_CHEAP, NORMAL, etc.)
|
||||||
|
language: The language code (defaults to English)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The localized price level if found in cache, None otherwise
|
||||||
|
|
||||||
|
"""
|
||||||
|
return get_translation(["sensor", "price_level", "price_levels", level], language)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,14 @@
|
||||||
"price_level": {
|
"price_level": {
|
||||||
"description": "Aktueller Preisstandanzeige (SEHR_GÜNSTIG bis SEHR_TEUER)",
|
"description": "Aktueller Preisstandanzeige (SEHR_GÜNSTIG bis SEHR_TEUER)",
|
||||||
"long_description": "Zeigt das aktuelle Preisniveau auf einer Skala von sehr günstig bis sehr teuer an",
|
"long_description": "Zeigt das aktuelle Preisniveau auf einer Skala von sehr günstig bis sehr teuer an",
|
||||||
"usage_tips": "Verwende dies für visuelle Anzeigen oder einfache Automatisierungen ohne Schwellenwertberechnung"
|
"usage_tips": "Verwende dies für visuelle Anzeigen oder einfache Automatisierungen ohne Schwellenwertberechnung",
|
||||||
|
"price_levels": {
|
||||||
|
"VERY_CHEAP": "Sehr Günstig",
|
||||||
|
"CHEAP": "Günstig",
|
||||||
|
"NORMAL": "Normal",
|
||||||
|
"EXPENSIVE": "Teuer",
|
||||||
|
"VERY_EXPENSIVE": "Sehr Teuer"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"lowest_price_today": {
|
"lowest_price_today": {
|
||||||
"description": "Der niedrigste Strompreis für den aktuellen Tag",
|
"description": "Der niedrigste Strompreis für den aktuellen Tag",
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,14 @@
|
||||||
"price_level": {
|
"price_level": {
|
||||||
"description": "Current price level indicator (VERY_CHEAP to VERY_EXPENSIVE)",
|
"description": "Current price level indicator (VERY_CHEAP to VERY_EXPENSIVE)",
|
||||||
"long_description": "Indicates the current price level on a scale from very cheap to very expensive",
|
"long_description": "Indicates the current price level on a scale from very cheap to very expensive",
|
||||||
"usage_tips": "Use this for visual indicators or simple automations without needing to calculate thresholds"
|
"usage_tips": "Use this for visual indicators or simple automations without needing to calculate thresholds",
|
||||||
|
"price_levels": {
|
||||||
|
"VERY_CHEAP": "Very Cheap",
|
||||||
|
"CHEAP": "Cheap",
|
||||||
|
"NORMAL": "Normal",
|
||||||
|
"EXPENSIVE": "Expensive",
|
||||||
|
"VERY_EXPENSIVE": "Very Expensive"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"lowest_price_today": {
|
"lowest_price_today": {
|
||||||
"description": "The lowest electricity price for the current day",
|
"description": "The lowest electricity price for the current day",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime, timedelta
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
@ -17,13 +17,13 @@ from homeassistant.const import CURRENCY_EURO, EntityCategory
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
PRICE_LEVEL_CHEAP,
|
CONF_EXTENDED_DESCRIPTIONS,
|
||||||
PRICE_LEVEL_EXPENSIVE,
|
DEFAULT_EXTENDED_DESCRIPTIONS,
|
||||||
|
DOMAIN,
|
||||||
PRICE_LEVEL_MAPPING,
|
PRICE_LEVEL_MAPPING,
|
||||||
PRICE_LEVEL_NORMAL,
|
|
||||||
PRICE_LEVEL_VERY_CHEAP,
|
|
||||||
PRICE_LEVEL_VERY_EXPENSIVE,
|
|
||||||
SENSOR_TYPE_PRICE_LEVEL,
|
SENSOR_TYPE_PRICE_LEVEL,
|
||||||
|
async_get_entity_description,
|
||||||
|
get_entity_description,
|
||||||
)
|
)
|
||||||
from .entity import TibberPricesEntity
|
from .entity import TibberPricesEntity
|
||||||
|
|
||||||
|
|
@ -314,8 +314,6 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
|
||||||
|
|
||||||
# Calculate the exact target datetime (not just the hour)
|
# Calculate the exact target datetime (not just the hour)
|
||||||
# This properly handles day boundaries
|
# This properly handles day boundaries
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
target_datetime = now.replace(microsecond=0) + timedelta(hours=hour_offset)
|
target_datetime = now.replace(microsecond=0) + timedelta(hours=hour_offset)
|
||||||
target_hour = target_datetime.hour
|
target_hour = target_datetime.hour
|
||||||
target_date = target_datetime.date()
|
target_date = target_datetime.date()
|
||||||
|
|
@ -468,13 +466,6 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
|
||||||
# Get user's language preference
|
# Get user's language preference
|
||||||
language = self.hass.config.language if self.hass.config.language else "en"
|
language = self.hass.config.language if self.hass.config.language else "en"
|
||||||
|
|
||||||
# Import only within the method to avoid circular imports
|
|
||||||
from .const import (
|
|
||||||
CONF_EXTENDED_DESCRIPTIONS,
|
|
||||||
DEFAULT_EXTENDED_DESCRIPTIONS,
|
|
||||||
async_get_entity_description,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add basic description
|
# Add basic description
|
||||||
description = await async_get_entity_description(self.hass, "sensor", base_key, language, "description")
|
description = await async_get_entity_description(self.hass, "sensor", base_key, language, "description")
|
||||||
if description:
|
if description:
|
||||||
|
|
@ -526,13 +517,6 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
|
||||||
# Get user's language preference
|
# Get user's language preference
|
||||||
language = self.hass.config.language if self.hass.config.language else "en"
|
language = self.hass.config.language if self.hass.config.language else "en"
|
||||||
|
|
||||||
# Import synchronous function to get cached descriptions
|
|
||||||
from .const import (
|
|
||||||
CONF_EXTENDED_DESCRIPTIONS,
|
|
||||||
DEFAULT_EXTENDED_DESCRIPTIONS,
|
|
||||||
get_entity_description,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add basic description from cache
|
# Add basic description from cache
|
||||||
description = get_entity_description("sensor", base_key, language, "description")
|
description = get_entity_description("sensor", base_key, language, "description")
|
||||||
if description:
|
if description:
|
||||||
|
|
@ -602,25 +586,66 @@ class TibberPricesSensor(TibberPricesEntity, SensorEntity):
|
||||||
self._add_price_level_attributes(attributes, current_hour_data["level"])
|
self._add_price_level_attributes(attributes, current_hour_data["level"])
|
||||||
|
|
||||||
def _add_price_level_attributes(self, attributes: dict, level: str) -> None:
|
def _add_price_level_attributes(self, attributes: dict, level: str) -> None:
|
||||||
"""Add price level specific attributes."""
|
"""
|
||||||
if level in PRICE_LEVEL_MAPPING:
|
Add price level specific attributes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
attributes: Dictionary to add attributes to
|
||||||
|
level: The price level value (e.g., VERY_CHEAP, NORMAL, etc.)
|
||||||
|
|
||||||
|
"""
|
||||||
|
if level not in PRICE_LEVEL_MAPPING:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Add numeric value for sorting/comparison
|
||||||
attributes["level_value"] = PRICE_LEVEL_MAPPING[level]
|
attributes["level_value"] = PRICE_LEVEL_MAPPING[level]
|
||||||
|
|
||||||
# Add human-readable level descriptions
|
# Add the original English level as a reliable identifier for automations
|
||||||
level_descriptions = {
|
attributes["level_id"] = level
|
||||||
PRICE_LEVEL_VERY_CHEAP: "Very low price compared to average",
|
|
||||||
PRICE_LEVEL_CHEAP: "Lower than average price",
|
# Default to the original level value if no translation is found
|
||||||
PRICE_LEVEL_NORMAL: "Average price level",
|
friendly_name = level
|
||||||
PRICE_LEVEL_EXPENSIVE: "Higher than average price",
|
|
||||||
PRICE_LEVEL_VERY_EXPENSIVE: "Very high price compared to average",
|
# Try to get localized friendly name from translations if Home Assistant is available
|
||||||
}
|
if self.hass:
|
||||||
if level in level_descriptions:
|
# Get user's language preference (default to English)
|
||||||
attributes["description"] = level_descriptions[level]
|
language = self.hass.config.language or "en"
|
||||||
|
|
||||||
|
# Use direct dictionary lookup for better performance and reliability
|
||||||
|
# This matches how async_get_entity_description works
|
||||||
|
cache_key = f"{DOMAIN}_translations_{language}"
|
||||||
|
if cache_key in self.hass.data:
|
||||||
|
translations = self.hass.data[cache_key]
|
||||||
|
|
||||||
|
# Navigate through the translation dictionary
|
||||||
|
if (
|
||||||
|
"sensor" in translations
|
||||||
|
and "price_level" in translations["sensor"]
|
||||||
|
and "price_levels" in translations["sensor"]["price_level"]
|
||||||
|
and level in translations["sensor"]["price_level"]["price_levels"]
|
||||||
|
):
|
||||||
|
friendly_name = translations["sensor"]["price_level"]["price_levels"][level]
|
||||||
|
|
||||||
|
# If we didn't find a translation in the current language, try English
|
||||||
|
if friendly_name == level and language != "en":
|
||||||
|
en_cache_key = f"{DOMAIN}_translations_en"
|
||||||
|
if en_cache_key in self.hass.data:
|
||||||
|
en_translations = self.hass.data[en_cache_key]
|
||||||
|
|
||||||
|
# Try using English translation as fallback
|
||||||
|
if (
|
||||||
|
"sensor" in en_translations
|
||||||
|
and "price_level" in en_translations["sensor"]
|
||||||
|
and "price_levels" in en_translations["sensor"]["price_level"]
|
||||||
|
and level in en_translations["sensor"]["price_level"]["price_levels"]
|
||||||
|
):
|
||||||
|
friendly_name = en_translations["sensor"]["price_level"]["price_levels"][level]
|
||||||
|
|
||||||
|
# Add the friendly name to attributes
|
||||||
|
attributes["friendly_name"] = friendly_name
|
||||||
|
|
||||||
def _add_next_hour_attributes(self, attributes: dict) -> None:
|
def _add_next_hour_attributes(self, attributes: dict) -> None:
|
||||||
"""Add attributes for next hour price sensors."""
|
"""Add attributes for next hour price sensors."""
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
price_info = self.coordinator.data["data"]["viewer"]["homes"][0]["currentSubscription"]["priceInfo"]
|
price_info = self.coordinator.data["data"]["viewer"]["homes"][0]["currentSubscription"]["priceInfo"]
|
||||||
now = dt_util.now()
|
now = dt_util.now()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue