Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate powerwall unique ids to use the gateway din #107509

Merged
merged 8 commits into from
Jan 14, 2024
28 changes: 27 additions & 1 deletion homeassistant/components/powerwall/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.aiohttp_client import async_create_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
Expand Down Expand Up @@ -151,7 +152,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
raise ConfigEntryNotReady from err

gateway_din = base_info.gateway_din
if gateway_din and entry.unique_id is not None and is_ip_address(entry.unique_id):
if entry.unique_id is not None and is_ip_address(entry.unique_id):
bdraco marked this conversation as resolved.
Show resolved Hide resolved
hass.config_entries.async_update_entry(entry, unique_id=gateway_din)

runtime_data = PowerwallRuntimeData(
Expand All @@ -178,11 +179,36 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

hass.data.setdefault(DOMAIN, {})[entry.entry_id] = runtime_data

await async_migrate_entity_unique_ids(hass, entry, base_info)

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

return True


async def async_migrate_entity_unique_ids(
hass: HomeAssistant, entry: ConfigEntry, base_info: PowerwallBaseInfo
) -> None:
"""Migrate old entity unique ids to use gateway_din."""
old_base_unique_id = "_".join(base_info.serial_numbers)
new_base_unique_id = base_info.gateway_din

dev_reg = dr.async_get(hass)
if device := dev_reg.async_get_device(identifiers={(DOMAIN, old_base_unique_id)}):
dev_reg.async_update_device(
device.id, new_identifiers={(DOMAIN, new_base_unique_id)}
)

ent_reg = er.async_get(hass)
for ent_entry in er.async_entries_for_config_entry(ent_reg, entry.entry_id):
current_unique_id = ent_entry.unique_id
if current_unique_id.startswith(old_base_unique_id):
new_unique_id = f"{new_base_unique_id}{current_unique_id.removeprefix(old_base_unique_id)}"
bdraco marked this conversation as resolved.
Show resolved Hide resolved
ent_reg.async_update_entity(
ent_entry.entity_id, new_unique_id=new_unique_id
)


async def _login_and_fetch_base_info(
power_wall: Powerwall, host: str, password: str | None
) -> PowerwallBaseInfo:
Expand Down
3 changes: 1 addition & 2 deletions homeassistant/components/powerwall/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ def __init__(self, powerwall_data: PowerwallRuntimeData) -> None:
assert coordinator is not None
super().__init__(coordinator)
self.power_wall = powerwall_data[POWERWALL_API]
# The serial numbers of the powerwalls are unique to every site
self.base_unique_id = "_".join(base_info.serial_numbers)
self.base_unique_id = base_info.gateway_din
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self.base_unique_id)},
manufacturer=MANUFACTURER,
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/powerwall/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
class PowerwallBaseInfo:
"""Base information for the powerwall integration."""

gateway_din: None | str
gateway_din: str
site_info: SiteInfoResponse
status: PowerwallStatusResponse
device_type: DeviceType
Expand Down
67 changes: 64 additions & 3 deletions tests/components/powerwall/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers import device_registry as dr, entity_registry as er
import homeassistant.util.dt as dt_util

from .mocks import _mock_powerwall_with_fixtures
from .mocks import MOCK_GATEWAY_DIN, _mock_powerwall_with_fixtures

from tests.common import MockConfigEntry, async_fire_time_changed

Expand All @@ -44,7 +44,7 @@ async def test_sensors(

device_registry = dr.async_get(hass)
reg_device = device_registry.async_get_device(
identifiers={("powerwall", "TG0123456789AB_TG9876543210BA")},
identifiers={("powerwall", MOCK_GATEWAY_DIN)},
)
assert reg_device.model == "PowerWall 2 (GW1)"
assert reg_device.sw_version == "1.50.1 c58c2df3"
Expand Down Expand Up @@ -173,3 +173,64 @@ async def test_sensors_with_empty_meters(hass: HomeAssistant) -> None:
await hass.async_block_till_done()

assert hass.states.get("sensor.mysite_solar_power") is None


async def test_unique_id_migrate(
hass: HomeAssistant, entity_registry_enabled_by_default: None
) -> None:
"""Test we can migrate unique ids of the sensors."""
device_registry = dr.async_get(hass)
ent_reg = er.async_get(hass)
config_entry = MockConfigEntry(domain=DOMAIN, data={CONF_IP_ADDRESS: "1.2.3.4"})
config_entry.add_to_hass(hass)

mock_powerwall = await _mock_powerwall_with_fixtures(hass)
old_unique_id = "_".join(sorted(["TG0123456789AB", "TG9876543210BA"]))
new_unique_id = MOCK_GATEWAY_DIN
device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id,
identifiers={("powerwall", old_unique_id)},
manufacturer="Tesla",
)
old_mysite_load_power_entity = ent_reg.async_get_or_create(
"sensor",
DOMAIN,
unique_id=f"{old_unique_id}_load_instant_power",
suggested_object_id="mysite_load_power",
config_entry=config_entry,
)
assert old_mysite_load_power_entity.entity_id == "sensor.mysite_load_power"

with patch(
"homeassistant.components.powerwall.config_flow.Powerwall",
return_value=mock_powerwall,
), patch(
"homeassistant.components.powerwall.Powerwall", return_value=mock_powerwall
):
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()

reg_device = device_registry.async_get_device(
identifiers={("powerwall", MOCK_GATEWAY_DIN)},
)
old_reg_device = device_registry.async_get_device(
identifiers={("powerwall", old_unique_id)},
)
assert old_reg_device is None
assert reg_device is not None

assert (
ent_reg.async_get_entity_id(
"sensor", DOMAIN, f"{old_unique_id}_load_instant_power"
)
is None
)
assert (
ent_reg.async_get_entity_id(
"sensor", DOMAIN, f"{new_unique_id}_load_instant_power"
)
is not None
)

state = hass.states.get("sensor.mysite_load_power")
assert state.state == "1.971"