diff --git a/homeassistant/components/asuswrt/router.py b/homeassistant/components/asuswrt/router.py index c782a8f0f3b..e0143d49259 100644 --- a/homeassistant/components/asuswrt/router.py +++ b/homeassistant/components/asuswrt/router.py @@ -20,7 +20,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from homeassistant.util import dt as dt_util +from homeassistant.util import dt as dt_util, slugify from .bridge import AsusWrtBridge, WrtDevice from .const import ( @@ -39,7 +39,6 @@ from .const import ( ) CONF_REQ_RELOAD = [CONF_DNSMASQ, CONF_INTERFACE, CONF_REQUIRE_IP] -DEFAULT_NAME = "Asuswrt" SCAN_INTERVAL = timedelta(seconds=30) @@ -179,6 +178,44 @@ class AsusWrtRouter: self.hass, dict(self._entry.data), self._options ) + def _migrate_entities_unique_id(self) -> None: + """Migrate router entities to new unique id format.""" + _ENTITY_MIGRATION_ID = { + "sensor_connected_device": "Devices Connected", + "sensor_rx_bytes": "Download", + "sensor_tx_bytes": "Upload", + "sensor_rx_rates": "Download Speed", + "sensor_tx_rates": "Upload Speed", + "sensor_load_avg1": "Load Avg (1m)", + "sensor_load_avg5": "Load Avg (5m)", + "sensor_load_avg15": "Load Avg (15m)", + "2.4GHz": "2.4GHz Temperature", + "5.0GHz": "5GHz Temperature", + "CPU": "CPU Temperature", + } + + entity_reg = er.async_get(self.hass) + router_entries = er.async_entries_for_config_entry( + entity_reg, self._entry.entry_id + ) + + migrate_entities: dict[str, str] = {} + for entry in router_entries: + if entry.domain == TRACKER_DOMAIN: + continue + old_unique_id = entry.unique_id + if not old_unique_id.startswith(DOMAIN): + continue + for new_id, old_id in _ENTITY_MIGRATION_ID.items(): + if old_unique_id.endswith(old_id): + migrate_entities[entry.entity_id] = slugify( + f"{self.unique_id}_{new_id}" + ) + break + + for entity_id, unique_id in migrate_entities.items(): + entity_reg.async_update_entity(entity_id, new_unique_id=unique_id) + async def setup(self) -> None: """Set up a AsusWrt router.""" try: @@ -215,6 +252,9 @@ class AsusWrtRouter: self._devices[device_mac] = AsusWrtDevInfo(device_mac, entry.original_name) + # Migrate entities to new unique id format + self._migrate_entities_unique_id() + # Update devices await self.update_devices() @@ -364,14 +404,9 @@ class AsusWrtRouter: return self._api.host @property - def unique_id(self) -> str | None: + def unique_id(self) -> str: """Return router unique id.""" - return self._entry.unique_id - - @property - def name(self) -> str: - """Return router name.""" - return self.host if self.unique_id else DEFAULT_NAME + return self._entry.unique_id or self._entry.entry_id @property def devices(self) -> dict[str, AsusWrtDevInfo]: diff --git a/homeassistant/components/asuswrt/sensor.py b/homeassistant/components/asuswrt/sensor.py index accd1eba59b..7f54bc29393 100644 --- a/homeassistant/components/asuswrt/sensor.py +++ b/homeassistant/components/asuswrt/sensor.py @@ -22,6 +22,7 @@ from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, ) +from homeassistant.util import slugify from .const import ( DATA_ASUSWRT, @@ -182,6 +183,9 @@ async def async_setup_entry( class AsusWrtSensor(CoordinatorEntity, SensorEntity): """Representation of a AsusWrt sensor.""" + entity_description: AsusWrtSensorEntityDescription + _attr_has_entity_name = True + def __init__( self, coordinator: DataUpdateCoordinator, @@ -190,13 +194,9 @@ class AsusWrtSensor(CoordinatorEntity, SensorEntity): ) -> None: """Initialize a AsusWrt sensor.""" super().__init__(coordinator) - self.entity_description: AsusWrtSensorEntityDescription = description + self.entity_description = description - self._attr_name = f"{router.name} {description.name}" - if router.unique_id: - self._attr_unique_id = f"{DOMAIN} {router.unique_id} {description.name}" - else: - self._attr_unique_id = f"{DOMAIN} {self.name}" + self._attr_unique_id = slugify(f"{router.unique_id}_{description.key}") self._attr_device_info = router.device_info self._attr_extra_state_attributes = {"hostname": router.host} diff --git a/tests/components/asuswrt/test_sensor.py b/tests/components/asuswrt/test_sensor.py index c28d71c1a29..2d7bda491a8 100644 --- a/tests/components/asuswrt/test_sensor.py +++ b/tests/components/asuswrt/test_sensor.py @@ -9,9 +9,13 @@ from homeassistant.components import device_tracker, sensor from homeassistant.components.asuswrt.const import ( CONF_INTERFACE, DOMAIN, + MODE_ROUTER, PROTOCOL_TELNET, + SENSORS_BYTES, + SENSORS_LOAD_AVG, + SENSORS_RATES, + SENSORS_TEMPERATURES, ) -from homeassistant.components.asuswrt.router import DEFAULT_NAME from homeassistant.components.device_tracker import CONF_CONSIDER_HOME from homeassistant.config_entries import ConfigEntryState from homeassistant.const import ( @@ -43,7 +47,7 @@ CONFIG_DATA = { CONF_PROTOCOL: PROTOCOL_TELNET, CONF_USERNAME: "user", CONF_PASSWORD: "pwd", - CONF_MODE: "router", + CONF_MODE: MODE_ROUTER, } MAC_ADDR = "a1:b2:c3:d4:e5:f6" @@ -57,26 +61,8 @@ MOCK_MAC_2 = "A2:B2:C2:D2:E2:F2" MOCK_MAC_3 = "A3:B3:C3:D3:E3:F3" MOCK_MAC_4 = "A4:B4:C4:D4:E4:F4" -SENSORS_DEFAULT = [ - "Download Speed", - "Download", - "Upload Speed", - "Upload", -] - -SENSORS_LOADAVG = [ - "Load Avg (1m)", - "Load Avg (5m)", - "Load Avg (15m)", -] - -SENSORS_TEMP = [ - "2.4GHz Temperature", - "5GHz Temperature", - "CPU Temperature", -] - -SENSORS_ALL = [*SENSORS_DEFAULT, *SENSORS_LOADAVG, *SENSORS_TEMP] +SENSORS_DEFAULT = [*SENSORS_BYTES, *SENSORS_RATES] +SENSORS_ALL = [*SENSORS_DEFAULT, *SENSORS_LOAD_AVG, *SENSORS_TEMPERATURES] PATCH_SETUP_ENTRY = patch( "homeassistant.components.asuswrt.async_setup_entry", @@ -105,7 +91,7 @@ def mock_available_temps_fixture(): @pytest.fixture(name="create_device_registry_devices") -def create_device_registry_devices_fixture(hass): +def create_device_registry_devices_fixture(hass: HomeAssistant): """Create device registry devices so the device tracker entities are enabled when added.""" dev_reg = dr.async_get(hass) config_entry = MockConfigEntry(domain="something_else") @@ -182,7 +168,7 @@ def mock_controller_connect_sens_fail(): yield service_mock -def _setup_entry(hass, config, sensors, unique_id=None): +def _setup_entry(hass: HomeAssistant, config, sensors, unique_id=None): """Create mock config entry with enabled sensors.""" entity_reg = er.async_get(hass) @@ -195,16 +181,17 @@ def _setup_entry(hass, config, sensors, unique_id=None): ) # init variable - obj_prefix = slugify(HOST if unique_id else DEFAULT_NAME) + obj_prefix = slugify(HOST) sensor_prefix = f"{sensor.DOMAIN}.{obj_prefix}" + unique_id_prefix = slugify(unique_id or config_entry.entry_id) # Pre-enable the status sensor - for sensor_name in sensors: - sensor_id = slugify(sensor_name) + for sensor_key in sensors: + sensor_id = slugify(sensor_key) entity_reg.async_get_or_create( sensor.DOMAIN, DOMAIN, - f"{DOMAIN} {unique_id or DEFAULT_NAME} {sensor_name}", + f"{unique_id_prefix}_{sensor_id}", suggested_object_id=f"{obj_prefix}_{sensor_id}", config_entry=config_entry, disabled_by=None, @@ -255,10 +242,10 @@ async def test_sensors( assert hass.states.get(f"{device_tracker.DOMAIN}.test").state == STATE_HOME assert hass.states.get(f"{device_tracker.DOMAIN}.testtwo").state == STATE_HOME - assert hass.states.get(f"{sensor_prefix}_download_speed").state == "160.0" - assert hass.states.get(f"{sensor_prefix}_download").state == "60.0" - assert hass.states.get(f"{sensor_prefix}_upload_speed").state == "80.0" - assert hass.states.get(f"{sensor_prefix}_upload").state == "50.0" + assert hass.states.get(f"{sensor_prefix}_sensor_rx_rates").state == "160.0" + assert hass.states.get(f"{sensor_prefix}_sensor_rx_bytes").state == "60.0" + assert hass.states.get(f"{sensor_prefix}_sensor_tx_rates").state == "80.0" + assert hass.states.get(f"{sensor_prefix}_sensor_tx_bytes").state == "50.0" assert hass.states.get(f"{sensor_prefix}_devices_connected").state == "2" # remove first tracked device @@ -296,7 +283,7 @@ async def test_loadavg_sensors( connect, ) -> None: """Test creating an AsusWRT load average sensors.""" - config_entry, sensor_prefix = _setup_entry(hass, CONFIG_DATA, SENSORS_LOADAVG) + config_entry, sensor_prefix = _setup_entry(hass, CONFIG_DATA, SENSORS_LOAD_AVG) config_entry.add_to_hass(hass) # initial devices setup @@ -306,9 +293,9 @@ async def test_loadavg_sensors( await hass.async_block_till_done() # assert temperature sensor available - assert hass.states.get(f"{sensor_prefix}_load_avg_1m").state == "1.1" - assert hass.states.get(f"{sensor_prefix}_load_avg_5m").state == "1.2" - assert hass.states.get(f"{sensor_prefix}_load_avg_15m").state == "1.3" + assert hass.states.get(f"{sensor_prefix}_sensor_load_avg1").state == "1.1" + assert hass.states.get(f"{sensor_prefix}_sensor_load_avg5").state == "1.2" + assert hass.states.get(f"{sensor_prefix}_sensor_load_avg15").state == "1.3" async def test_temperature_sensors( @@ -316,7 +303,7 @@ async def test_temperature_sensors( connect, ) -> None: """Test creating a AsusWRT temperature sensors.""" - config_entry, sensor_prefix = _setup_entry(hass, CONFIG_DATA, SENSORS_TEMP) + config_entry, sensor_prefix = _setup_entry(hass, CONFIG_DATA, SENSORS_TEMPERATURES) config_entry.add_to_hass(hass) # initial devices setup @@ -326,9 +313,9 @@ async def test_temperature_sensors( await hass.async_block_till_done() # assert temperature sensor available - assert hass.states.get(f"{sensor_prefix}_2_4ghz_temperature").state == "40.0" - assert not hass.states.get(f"{sensor_prefix}_5ghz_temperature") - assert hass.states.get(f"{sensor_prefix}_cpu_temperature").state == "71.2" + assert hass.states.get(f"{sensor_prefix}_2_4ghz").state == "40.0" + assert not hass.states.get(f"{sensor_prefix}_5_0ghz") + assert hass.states.get(f"{sensor_prefix}_cpu").state == "71.2" @pytest.mark.parametrize( @@ -396,3 +383,31 @@ async def test_options_reload(hass: HomeAssistant, connect) -> None: assert setup_entry_call.called assert config_entry.state is ConfigEntryState.LOADED + + +async def test_unique_id_migration(hass: HomeAssistant, connect) -> None: + """Test AsusWRT entities unique id format migration.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data=CONFIG_DATA, + unique_id=MAC_ADDR, + ) + config_entry.add_to_hass(hass) + + entity_reg = er.async_get(hass) + obj_entity_id = slugify(f"{HOST} Upload") + entity_reg.async_get_or_create( + sensor.DOMAIN, + DOMAIN, + f"{DOMAIN} {MAC_ADDR} Upload", + suggested_object_id=obj_entity_id, + config_entry=config_entry, + disabled_by=None, + ) + + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + migr_entity = entity_reg.async_get(f"{sensor.DOMAIN}.{obj_entity_id}") + assert migr_entity is not None + assert migr_entity.unique_id == slugify(f"{MAC_ADDR}_sensor_tx_bytes")