add friendly name

This commit is contained in:
Julian Pawlowski 2025-04-23 23:53:11 +00:00
parent 02a226819a
commit 94ef6ed4a6
4 changed files with 207 additions and 61 deletions

View file

@ -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)

View file

@ -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",

View file

@ -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",

View file

@ -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()