Voip migrate entities (#136140)

* Migrate VoIP entities

* Revert device name to host again
This commit is contained in:
Paulus Schoutsen 2025-01-21 12:12:30 -05:00 committed by GitHub
parent dd31c2c832
commit 22e0b0e9a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 65 additions and 30 deletions

View File

@ -4,6 +4,7 @@ from __future__ import annotations
from collections.abc import Callable, Iterator from collections.abc import Callable, Iterator
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Any
from voip_utils import CallInfo, VoipDatagramProtocol from voip_utils import CallInfo, VoipDatagramProtocol
@ -144,19 +145,39 @@ class VoIPDevices:
if voip_device is None: if voip_device is None:
# If we couldn't find the device based on SIP URI, see if we can # If we couldn't find the device based on SIP URI, see if we can
# find an old device based on just the host/IP and migrate it # find an old device based on just the host/IP and migrate it
voip_device = self.devices.get(call_info.caller_endpoint.host) old_id = call_info.caller_endpoint.host
voip_device = self.devices.get(old_id)
if voip_device is not None: if voip_device is not None:
voip_device.voip_id = voip_id voip_device.voip_id = voip_id
self.devices[voip_id] = voip_device self.devices[voip_id] = voip_device
dev_reg.async_update_device( dev_reg.async_update_device(
voip_device.device_id, new_identifiers={(DOMAIN, voip_id)} voip_device.device_id, new_identifiers={(DOMAIN, voip_id)}
) )
# Migrate entities
old_prefix = f"{old_id}-"
def entity_migrator(entry: er.RegistryEntry) -> dict[str, Any] | None:
"""Migrate entities."""
if not entry.unique_id.startswith(old_prefix):
return None
key = entry.unique_id[len(old_prefix) :]
return {
"new_unique_id": f"{voip_id}-{key}",
}
self.config_entry.async_create_task(
self.hass,
er.async_migrate_entries(
self.hass, self.config_entry.entry_id, entity_migrator
),
f"voip migrating entities {voip_id}",
)
# Update device with latest info # Update device with latest info
device = dev_reg.async_get_or_create( device = dev_reg.async_get_or_create(
config_entry_id=self.config_entry.entry_id, config_entry_id=self.config_entry.entry_id,
identifiers={(DOMAIN, voip_id)}, identifiers={(DOMAIN, voip_id)},
name=voip_id, name=call_info.caller_endpoint.host,
manufacturer=manuf, manufacturer=manuf,
model=model, model=model,
sw_version=fw_version, sw_version=fw_version,

View File

@ -22,18 +22,18 @@ async def test_call_in_progress(
voip_device: VoIPDevice, voip_device: VoIPDevice,
) -> None: ) -> None:
"""Test call in progress.""" """Test call in progress."""
state = hass.states.get("binary_sensor.sip_192_168_1_210_5060_call_in_progress") state = hass.states.get("binary_sensor.192_168_1_210_call_in_progress")
assert state is not None assert state is not None
assert state.state == "off" assert state.state == "off"
voip_device.set_is_active(True) voip_device.set_is_active(True)
state = hass.states.get("binary_sensor.sip_192_168_1_210_5060_call_in_progress") state = hass.states.get("binary_sensor.192_168_1_210_call_in_progress")
assert state.state == "on" assert state.state == "on"
voip_device.set_is_active(False) voip_device.set_is_active(False)
state = hass.states.get("binary_sensor.sip_192_168_1_210_5060_call_in_progress") state = hass.states.get("binary_sensor.192_168_1_210_call_in_progress")
assert state.state == "off" assert state.state == "off"
@ -45,9 +45,9 @@ async def test_assist_in_progress_disabled_by_default(
) -> None: ) -> None:
"""Test assist in progress binary sensor is added disabled.""" """Test assist in progress binary sensor is added disabled."""
assert not hass.states.get("binary_sensor.sip_192_168_1_210_5060_call_in_progress") assert not hass.states.get("binary_sensor.192_168_1_210_call_in_progress")
entity_entry = entity_registry.async_get( entity_entry = entity_registry.async_get(
"binary_sensor.sip_192_168_1_210_5060_call_in_progress" "binary_sensor.192_168_1_210_call_in_progress"
) )
assert entity_entry assert entity_entry
assert entity_entry.disabled assert entity_entry.disabled
@ -63,7 +63,7 @@ async def test_assist_in_progress_issue(
) -> None: ) -> None:
"""Test assist in progress binary sensor.""" """Test assist in progress binary sensor."""
call_in_progress_entity_id = "binary_sensor.sip_192_168_1_210_5060_call_in_progress" call_in_progress_entity_id = "binary_sensor.192_168_1_210_call_in_progress"
state = hass.states.get(call_in_progress_entity_id) state = hass.states.get(call_in_progress_entity_id)
assert state is not None assert state is not None
@ -96,7 +96,7 @@ async def test_assist_in_progress_repair_flow(
) -> None: ) -> None:
"""Test assist in progress binary sensor deprecation issue flow.""" """Test assist in progress binary sensor deprecation issue flow."""
call_in_progress_entity_id = "binary_sensor.sip_192_168_1_210_5060_call_in_progress" call_in_progress_entity_id = "binary_sensor.192_168_1_210_call_in_progress"
state = hass.states.get(call_in_progress_entity_id) state = hass.states.get(call_in_progress_entity_id)
assert state is not None assert state is not None

View File

@ -8,7 +8,7 @@ from voip_utils import CallInfo
from homeassistant.components.voip import DOMAIN from homeassistant.components.voip import DOMAIN
from homeassistant.components.voip.devices import VoIPDevice, VoIPDevices from homeassistant.components.voip.devices import VoIPDevice, VoIPDevices
from homeassistant.core import HomeAssistant 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
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@ -27,7 +27,7 @@ async def test_device_registry_info(
identifiers={(DOMAIN, call_info.caller_endpoint.uri)} identifiers={(DOMAIN, call_info.caller_endpoint.uri)}
) )
assert device is not None assert device is not None
assert device.name == call_info.caller_endpoint.uri assert device.name == call_info.caller_endpoint.host
assert device.manufacturer == "Grandstream" assert device.manufacturer == "Grandstream"
assert device.model == "HT801" assert device.model == "HT801"
assert device.sw_version == "1.0.17.5" assert device.sw_version == "1.0.17.5"
@ -71,47 +71,61 @@ async def test_remove_device_registry_entry(
) -> None: ) -> None:
"""Test removing a device registry entry.""" """Test removing a device registry entry."""
assert voip_device.voip_id in voip_devices.devices assert voip_device.voip_id in voip_devices.devices
assert hass.states.get("switch.sip_192_168_1_210_5060_allow_calls") is not None assert hass.states.get("switch.192_168_1_210_allow_calls") is not None
device_registry.async_remove_device(voip_device.device_id) device_registry.async_remove_device(voip_device.device_id)
await hass.async_block_till_done() await hass.async_block_till_done()
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get("switch.sip_192_168_1_210_5060_allow_calls") is None assert hass.states.get("switch.192_168_1_210_allow_calls") is None
assert voip_device.voip_id not in voip_devices.devices assert voip_device.voip_id not in voip_devices.devices
@pytest.fixture @pytest.fixture
async def legacy_dev_reg_entry( async def legacy_dev_reg_entry(
entity_registry: er.EntityRegistry,
device_registry: dr.DeviceRegistry, device_registry: dr.DeviceRegistry,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
call_info: CallInfo, call_info: CallInfo,
) -> None: ) -> None:
"""Fixture to run before we set up the VoIP integration via fixture.""" """Fixture to run before we set up the VoIP integration via fixture."""
return device_registry.async_get_or_create( device = device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id, config_entry_id=config_entry.entry_id,
identifiers={(DOMAIN, call_info.caller_ip)}, identifiers={(DOMAIN, call_info.caller_ip)},
) )
entity_registry.async_get_or_create(
"switch",
DOMAIN,
f"{call_info.caller_ip}-allow_calls",
device_id=device.id,
config_entry=config_entry,
)
return device
async def test_device_registry_migation( async def test_device_registry_migration(
hass: HomeAssistant, hass: HomeAssistant,
legacy_dev_reg_entry: dr.DeviceEntry, legacy_dev_reg_entry: dr.DeviceEntry,
voip_devices: VoIPDevices, voip_devices: VoIPDevices,
call_info: CallInfo, call_info: CallInfo,
entity_registry: er.EntityRegistry,
device_registry: dr.DeviceRegistry, device_registry: dr.DeviceRegistry,
) -> None: ) -> None:
"""Test info in device registry migrates old devices.""" """Test info in device registry migrates old devices."""
voip_device = voip_devices.async_get_or_create(call_info) voip_device = voip_devices.async_get_or_create(call_info)
assert voip_device.voip_id == call_info.caller_endpoint.uri new_id = call_info.caller_endpoint.uri
assert voip_device.voip_id == new_id
device = device_registry.async_get_device( device = device_registry.async_get_device(identifiers={(DOMAIN, new_id)})
identifiers={(DOMAIN, call_info.caller_endpoint.uri)}
)
assert device is not None assert device is not None
assert device.id == legacy_dev_reg_entry.id assert device.id == legacy_dev_reg_entry.id
assert device.identifiers == {(DOMAIN, call_info.caller_endpoint.uri)} assert device.identifiers == {(DOMAIN, new_id)}
assert device.name == call_info.caller_endpoint.uri assert device.name == call_info.caller_endpoint.host
assert device.manufacturer == "Grandstream" assert device.manufacturer == "Grandstream"
assert device.model == "HT801" assert device.model == "HT801"
assert device.sw_version == "1.0.17.5" assert device.sw_version == "1.0.17.5"
assert (
entity_registry.async_get_entity_id("switch", DOMAIN, f"{new_id}-allow_calls")
is not None
)

View File

@ -15,7 +15,7 @@ async def test_pipeline_select(
Functionality is tested in assist_pipeline/test_select.py. Functionality is tested in assist_pipeline/test_select.py.
This test is only to ensure it is set up. This test is only to ensure it is set up.
""" """
state = hass.states.get("select.sip_192_168_1_210_5060_assistant") state = hass.states.get("select.192_168_1_210_assistant")
assert state is not None assert state is not None
assert state.state == "preferred" assert state.state == "preferred"
@ -30,6 +30,6 @@ async def test_vad_sensitivity_select(
Functionality is tested in assist_pipeline/test_select.py. Functionality is tested in assist_pipeline/test_select.py.
This test is only to ensure it is set up. This test is only to ensure it is set up.
""" """
state = hass.states.get("select.sip_192_168_1_210_5060_finished_speaking_detection") state = hass.states.get("select.192_168_1_210_finished_speaking_detection")
assert state is not None assert state is not None
assert state.state == "default" assert state.state == "default"

View File

@ -13,41 +13,41 @@ async def test_allow_call(
"""Test allow call.""" """Test allow call."""
assert not voip_device.async_allow_call(hass) assert not voip_device.async_allow_call(hass)
state = hass.states.get("switch.sip_192_168_1_210_5060_allow_calls") state = hass.states.get("switch.192_168_1_210_allow_calls")
assert state is not None assert state is not None
assert state.state == "off" assert state.state == "off"
await hass.config_entries.async_reload(config_entry.entry_id) await hass.config_entries.async_reload(config_entry.entry_id)
state = hass.states.get("switch.sip_192_168_1_210_5060_allow_calls") state = hass.states.get("switch.192_168_1_210_allow_calls")
assert state.state == "off" assert state.state == "off"
await hass.services.async_call( await hass.services.async_call(
"switch", "switch",
"turn_on", "turn_on",
{"entity_id": "switch.sip_192_168_1_210_5060_allow_calls"}, {"entity_id": "switch.192_168_1_210_allow_calls"},
blocking=True, blocking=True,
) )
assert voip_device.async_allow_call(hass) assert voip_device.async_allow_call(hass)
state = hass.states.get("switch.sip_192_168_1_210_5060_allow_calls") state = hass.states.get("switch.192_168_1_210_allow_calls")
assert state.state == "on" assert state.state == "on"
await hass.config_entries.async_reload(config_entry.entry_id) await hass.config_entries.async_reload(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get("switch.sip_192_168_1_210_5060_allow_calls") state = hass.states.get("switch.192_168_1_210_allow_calls")
assert state.state == "on" assert state.state == "on"
await hass.services.async_call( await hass.services.async_call(
"switch", "switch",
"turn_off", "turn_off",
{"entity_id": "switch.sip_192_168_1_210_5060_allow_calls"}, {"entity_id": "switch.192_168_1_210_allow_calls"},
blocking=True, blocking=True,
) )
assert not voip_device.async_allow_call(hass) assert not voip_device.async_allow_call(hass)
state = hass.states.get("switch.sip_192_168_1_210_5060_allow_calls") state = hass.states.get("switch.192_168_1_210_allow_calls")
assert state.state == "off" assert state.state == "off"