Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 28 additions & 8 deletions custom_components/givenergy_local/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,25 +363,31 @@ async def _missing_dashboard_cards(hass: HomeAssistant) -> list[str]:
# valid — IR35 was always AC charge, merely mislabelled "load" — so re-point the
# existing registry entry to the new unique_id, carrying its history, statistics
# and customisations across rather than orphaning it and starting fresh.
_RENAMED_UNIQUE_ID_SUFFIXES = {

# Values: (new_uid_suffix, old_entity_id_slug | None).
# old_entity_id_slug is the name-slug the entity carried before renaming; None
# means no entity_id rename is needed (unique_id suffix change only).
_RENAMED_UNIQUE_ID_SUFFIXES: dict[str, tuple[str, str | None]] = {
# givenergy-modbus #174 (2.1.1): IR35 was AC charge, not house load.
"e_load_day": "e_ac_charge_today",
"e_load_day": ("e_ac_charge_today", None),
# givenergy-modbus #174/#176 (2.1.2): IR44/IR45-46 are PV generation, not
# inverter AC output. Move both sensors together so today+total stay paired.
"e_inverter_out_day": "e_pv_generation_today",
"e_inverter_out_total": "e_pv_generation_total",
"e_inverter_out_day": ("e_pv_generation_today", None),
"e_inverter_out_total": ("e_pv_generation_total", None),
# #52: p_grid_out (IR30) is a signed net flow, not export-only — rename the
# surfaced entity to "Grid Power" to match. Existing history is valid (the
# underlying register hasn't changed), so re-point in place.
"p_grid_out": "grid_power",
# entity_id was "…_grid_export_power"; must also be renamed so dashboard
# references to "…_grid_power" resolve correctly.
"p_grid_out": ("grid_power", "grid_export_power"),
}


def _migrate_unique_ids(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Re-point entities registered under a renamed unique_id suffix in place."""
registry = er.async_get(hass)
for ent in er.async_entries_for_config_entry(registry, entry.entry_id):
for old, new in _RENAMED_UNIQUE_ID_SUFFIXES.items():
for old, (new, old_slug) in _RENAMED_UNIQUE_ID_SUFFIXES.items():
if not ent.unique_id.endswith(f"_{old}"):
continue
new_uid = ent.unique_id[: -len(old)] + new
Expand All @@ -394,8 +400,22 @@ def _migrate_unique_ids(hass: HomeAssistant, entry: ConfigEntry) -> None:
new_uid,
)
break
_LOGGER.info("Migrating unique_id %s -> %s", ent.unique_id, new_uid)
registry.async_update_entity(ent.entity_id, new_unique_id=new_uid)
new_entity_id = (
ent.entity_id[: -len(old_slug)] + new
if old_slug and ent.entity_id.endswith(f"_{old_slug}")
else None
)
_LOGGER.info(
"Migrating unique_id %s -> %s%s",
ent.unique_id,
new_uid,
f" (entity_id: {ent.entity_id} -> {new_entity_id})" if new_entity_id else "",
)
registry.async_update_entity(
ent.entity_id,
new_unique_id=new_uid,
**({"new_entity_id": new_entity_id} if new_entity_id else {}),
)
Comment on lines +403 to +418

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

When performing entity migrations in Home Assistant, we must preserve the existing entity_id and only update the unique_id. Renaming the entity_id can break user automations, scripts, and templates that reference the old entity ID. Please update the migration logic to only modify the unique_id and leave the entity_id unchanged.

            _LOGGER.info(
                "Migrating unique_id %s -> %s",
                ent.unique_id,
                new_uid,
            )
            registry.async_update_entity(
                ent.entity_id,
                new_unique_id=new_uid,
            )
References
  1. When performing entity migrations in Home Assistant, preserve the existing entity_id and only update the unique_id to prevent breaking user automations and templates that reference the old entity ID.

break


Expand Down
28 changes: 28 additions & 0 deletions tests/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -562,3 +562,31 @@ async def test_unique_id_migration_repoints_grid_power(hass, mock_client, mock_c
migrated = registry.async_get(old.entity_id)
assert migrated is not None, f"entity_id {old.entity_id!r} lost after migration"
assert migrated.unique_id == "SA1234G123_grid_power"


async def test_unique_id_migration_also_renames_grid_export_power_entity_id(
hass, mock_client, mock_config_entry
):
"""Migration of p_grid_out → grid_power also renames the entity_id when it
still carries the old '_grid_export_power' slug, so dashboard references to
'…_grid_power' resolve to the existing entity rather than a missing one."""
mock_config_entry.add_to_hass(hass)
registry = er.async_get(hass)
old = registry.async_get_or_create(
"sensor", DOMAIN, "SA1234G123_p_grid_out", config_entry=mock_config_entry
)
# Simulate the entity_id that HA would have assigned when the sensor was
# first registered under the old "Grid Export Power" name.
old_entity_id = "sensor.givenergy_inverter_sa1234g123_grid_export_power"
registry.async_update_entity(old.entity_id, new_entity_id=old_entity_id)

await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()

# Old entity_id must be gone and re-issued under the new slug.
assert registry.async_get(old_entity_id) is None
new_entity_id = "sensor.givenergy_inverter_sa1234g123_grid_power"
migrated = registry.async_get(new_entity_id)
assert migrated is not None, f"entity_id {new_entity_id!r} not found after migration"
assert migrated.unique_id == "SA1234G123_grid_power"
assert migrated.entity_id == new_entity_id
Loading