mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-05-28 18:43:40 +00:00
Adds migrations.py with automatic entity registry migration for renamed sensor keys. Separated from coordinator/repairs.py (runtime issues) and __init__.py _migrate_config_options() (config format changes). - ENTITY_KEY_RENAMES dict maps old→new entity keys (extensible) - _auto_migrate_entity_keys() updates unique_id, preserves entity_id - Handles partial migration (new entity already exists → remove old) - Creates persistent HA repair issue after migration via ir.async_create_issue() - Called in async_setup_entry() after _migrate_config_options() Migrates: trend_change_in_minutes → next_price_trend_change_in Repair issue informs users about: - Auto-migrated entity renames (entity_id preserved, no action needed) - Duration sensor value unit change (hours → minutes): update automation thresholds from `state < 0.25` to `state < 15` for 15-minute checks All 5 language files (en, de, nb, nl, sv) updated with translations. BREAKING CHANGE: Duration sensors (remaining time, starts in, period duration, trend change countdown) now report state values in minutes instead of hours. Display unit in dashboards remains hours by default. Update numeric comparisons in automations accordingly. Impact: Users upgrading from previous releases see an informational repair notice guiding them through any required automation updates. Entity renames are handled transparently with no loss of history.
147 lines
4.8 KiB
Python
147 lines
4.8 KiB
Python
"""
|
|
Entity migration checks for Tibber Prices integration.
|
|
|
|
Detects obsolete entity keys in the entity registry after upgrades and
|
|
performs automatic migration where possible. Creates repair issues to
|
|
notify users about breaking changes that require manual action.
|
|
|
|
Separation of concerns:
|
|
- This module: One-time upgrade migrations (entity renames, breaking changes)
|
|
- coordinator/repairs.py: Runtime repairs (API issues, missing data)
|
|
- __init__.py _migrate_config_options(): Config option format changes
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import TYPE_CHECKING
|
|
|
|
from homeassistant.core import callback
|
|
from homeassistant.helpers import entity_registry as er
|
|
from homeassistant.helpers import issue_registry as ir
|
|
|
|
from .const import DOMAIN
|
|
|
|
if TYPE_CHECKING:
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.core import HomeAssistant
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
# ============================================================================
|
|
# ENTITY KEY RENAMES
|
|
# Add entries here when renaming sensors in future releases.
|
|
# old_entity_key -> new_entity_key (auto-migrated, entity_id preserved)
|
|
# ============================================================================
|
|
ENTITY_KEY_RENAMES: dict[str, str] = {
|
|
"trend_change_in_minutes": "next_price_trend_change_in",
|
|
}
|
|
|
|
|
|
@callback
|
|
def check_entity_migrations(
|
|
hass: HomeAssistant,
|
|
entry: ConfigEntry,
|
|
) -> None:
|
|
"""
|
|
Check for entity migrations and create repairs if needed.
|
|
|
|
Called during async_setup_entry, before platform forwarding.
|
|
Performs auto-migration of renamed entities and creates
|
|
informational repairs about breaking changes.
|
|
"""
|
|
ent_reg = er.async_get(hass)
|
|
|
|
# Auto-migrate renamed entity keys
|
|
migrated = _auto_migrate_entity_keys(ent_reg, entry)
|
|
|
|
# Create persistent repair about breaking changes
|
|
issue_id = f"entity_migration_{entry.entry_id}"
|
|
|
|
if migrated:
|
|
rename_lines = [f"- `{old_key}` → `{new_key}`" for old_key, new_key, _ in migrated]
|
|
entity_list = "\n".join(rename_lines)
|
|
|
|
_LOGGER.info(
|
|
"Auto-migrated %d entity key(s) for '%s': %s",
|
|
len(migrated),
|
|
entry.title,
|
|
", ".join(f"{old} → {new}" for old, new, _ in migrated),
|
|
)
|
|
|
|
ir.async_create_issue(
|
|
hass,
|
|
DOMAIN,
|
|
issue_id,
|
|
is_fixable=False,
|
|
is_persistent=True,
|
|
severity=ir.IssueSeverity.WARNING,
|
|
translation_key="entity_migration",
|
|
translation_placeholders={
|
|
"home_name": entry.title,
|
|
"entity_list": entity_list,
|
|
"count": str(len(migrated)),
|
|
},
|
|
learn_more_url="https://github.com/jpawlowski/hass.tibber_prices/releases",
|
|
)
|
|
|
|
|
|
@callback
|
|
def _auto_migrate_entity_keys(
|
|
ent_reg: er.EntityRegistry,
|
|
entry: ConfigEntry,
|
|
) -> list[tuple[str, str, str]]:
|
|
"""
|
|
Auto-migrate renamed entity keys in the entity registry.
|
|
|
|
Updates unique_ids for renamed entities while preserving entity_id
|
|
and all user customizations (history, dashboard references, etc.).
|
|
|
|
Returns:
|
|
List of (old_key, new_key, entity_id) tuples for migrated entities
|
|
|
|
"""
|
|
migrated: list[tuple[str, str, str]] = []
|
|
prefix = f"{entry.entry_id}_"
|
|
|
|
# Get all entities for this config entry
|
|
entry_entities = er.async_entries_for_config_entry(ent_reg, entry.entry_id)
|
|
|
|
for entity_entry in entry_entities:
|
|
if not entity_entry.unique_id.startswith(prefix):
|
|
continue
|
|
|
|
entity_key = entity_entry.unique_id[len(prefix) :]
|
|
if entity_key not in ENTITY_KEY_RENAMES:
|
|
continue
|
|
|
|
new_key = ENTITY_KEY_RENAMES[entity_key]
|
|
new_unique_id = f"{prefix}{new_key}"
|
|
|
|
# Check if new entity already exists (e.g., from a partial migration)
|
|
new_entity_id = ent_reg.async_get_entity_id(entity_entry.domain, DOMAIN, new_unique_id)
|
|
|
|
if new_entity_id:
|
|
# New entity already exists — remove the obsolete old one
|
|
_LOGGER.debug(
|
|
"Removing obsolete entity '%s' (new entity '%s' already exists)",
|
|
entity_entry.entity_id,
|
|
new_entity_id,
|
|
)
|
|
ent_reg.async_remove(entity_entry.entity_id)
|
|
else:
|
|
# Migrate: update unique_id (preserves entity_id and history)
|
|
_LOGGER.debug(
|
|
"Migrating entity '%s': unique_id '%s' → '%s'",
|
|
entity_entry.entity_id,
|
|
entity_entry.unique_id,
|
|
new_unique_id,
|
|
)
|
|
ent_reg.async_update_entity(
|
|
entity_entry.entity_id,
|
|
new_unique_id=new_unique_id,
|
|
)
|
|
|
|
migrated.append((entity_key, new_key, entity_entry.entity_id))
|
|
|
|
return migrated
|