mirror of
https://github.com/home-assistant/core.git
synced 2026-08-28 10:16:02 -05:00
Migrate wifi switch unique_id for Fritz (#166751)
This commit is contained in:
@@ -80,6 +80,5 @@ FRITZ_EXCEPTIONS = (
|
||||
|
||||
FRITZ_AUTH_EXCEPTIONS = (FritzAuthorizationError, FritzSecurityError)
|
||||
|
||||
WIFI_STANDARD = {1: "2.4Ghz", 2: "5Ghz", 3: "5Ghz", 4: "Guest"}
|
||||
|
||||
CONNECTION_TYPE_LAN = "LAN"
|
||||
|
||||
@@ -169,6 +169,18 @@
|
||||
"switch": {
|
||||
"internet_access": {
|
||||
"name": "Internet access"
|
||||
},
|
||||
"wi_fi_guest": {
|
||||
"name": "Guest"
|
||||
},
|
||||
"wi_fi_main_2_4ghz": {
|
||||
"name": "Main 2.4 GHz"
|
||||
},
|
||||
"wi_fi_main_5ghz": {
|
||||
"name": "Main 5 GHz"
|
||||
},
|
||||
"wi_fi_main_5ghz_high_6ghz": {
|
||||
"name": "Main 5 GHz High / 6 GHz"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -9,6 +9,7 @@ from homeassistant.components.network import async_get_source_ip
|
||||
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
@@ -22,8 +23,8 @@ from .const import (
|
||||
SWITCH_TYPE_PORTFORWARD,
|
||||
SWITCH_TYPE_PROFILE,
|
||||
SWITCH_TYPE_WIFINETWORK,
|
||||
WIFI_STANDARD,
|
||||
MeshRoles,
|
||||
Platform,
|
||||
)
|
||||
from .coordinator import FRITZ_DATA_KEY, AvmWrapper, FritzConfigEntry, FritzData
|
||||
from .entity import FritzBoxBaseEntity
|
||||
@@ -35,6 +36,101 @@ _LOGGER = logging.getLogger(__name__)
|
||||
# Set a sane value to avoid too many updates
|
||||
PARALLEL_UPDATES = 5
|
||||
|
||||
WIFI_STANDARD = {1: "2.4Ghz", 2: "5Ghz", 3: "5Ghz", 4: "Guest"}
|
||||
|
||||
WIFI_BAND = {
|
||||
0: {"band": "2.4Ghz"},
|
||||
1: {"band": "5Ghz"},
|
||||
3: {"band": "5Ghz High / 6Ghz"},
|
||||
}
|
||||
|
||||
|
||||
def _wifi_naming(
|
||||
network_info: dict[str, Any], wifi_index: int, wifi_count: int
|
||||
) -> str | None:
|
||||
"""Return a friendly name for a Wi-Fi network."""
|
||||
|
||||
if wifi_index == 2 and wifi_count == 4:
|
||||
# In case of 4 Wi-Fi networks, the 2nd one is used for internal communication
|
||||
# between mesh devices and should not be named like the others to avoid confusion
|
||||
return None
|
||||
|
||||
if (wifi_index + 1) == wifi_count:
|
||||
# Last Wi-Fi network in the guest network, both bands available
|
||||
return "Guest"
|
||||
|
||||
# Cast to correct type for type checker
|
||||
if (result := WIFI_BAND.get(wifi_index)) is not None:
|
||||
return f"Main {result['band']}"
|
||||
|
||||
return None
|
||||
|
||||
|
||||
async def _get_wifi_networks_list(avm_wrapper: AvmWrapper) -> dict[int, dict[str, Any]]:
|
||||
"""Get a list of wifi networks with friendly names."""
|
||||
wifi_count = len(
|
||||
[
|
||||
s
|
||||
for s in avm_wrapper.connection.services
|
||||
if s.startswith("WLANConfiguration")
|
||||
]
|
||||
)
|
||||
_LOGGER.debug("WiFi networks count: %s", wifi_count)
|
||||
networks: dict[int, dict[str, Any]] = {}
|
||||
for i in range(1, wifi_count + 1):
|
||||
network_info = await avm_wrapper.async_get_wlan_configuration(i)
|
||||
if (switch_name := _wifi_naming(network_info, i - 1, wifi_count)) is None:
|
||||
continue
|
||||
networks[i] = network_info
|
||||
networks[i]["switch_name"] = switch_name
|
||||
|
||||
_LOGGER.debug("WiFi networks list: %s", networks)
|
||||
return networks
|
||||
|
||||
|
||||
async def _migrate_to_new_unique_id(
|
||||
hass: HomeAssistant, avm_wrapper: AvmWrapper
|
||||
) -> None:
|
||||
"""Migrate old unique ids to new unique ids."""
|
||||
|
||||
_LOGGER.debug("Migrating Wi-Fi switches")
|
||||
entity_registry = er.async_get(hass)
|
||||
|
||||
networks = await _get_wifi_networks_list(avm_wrapper)
|
||||
for index, network in networks.items():
|
||||
description = f"Wi-Fi {network['NewSSID']}"
|
||||
if (
|
||||
len(
|
||||
[
|
||||
j
|
||||
for j, n in networks.items()
|
||||
if slugify(n["NewSSID"]) == slugify(network["NewSSID"])
|
||||
]
|
||||
)
|
||||
> 1
|
||||
):
|
||||
description += f" ({WIFI_STANDARD[index]})"
|
||||
|
||||
old_unique_id = f"{avm_wrapper.unique_id}-{slugify(description)}"
|
||||
new_unique_id = f"{avm_wrapper.unique_id}-wi_fi_{slugify(_wifi_naming(network, index - 1, len(networks)))}"
|
||||
|
||||
entity_id = entity_registry.async_get_entity_id(
|
||||
Platform.SWITCH, DOMAIN, old_unique_id
|
||||
)
|
||||
|
||||
if entity_id is not None:
|
||||
entity_registry.async_update_entity(
|
||||
entity_id,
|
||||
new_unique_id=new_unique_id,
|
||||
)
|
||||
_LOGGER.debug(
|
||||
"Migrating Wi-FI switch unique_id from [%s] to [%s]",
|
||||
old_unique_id,
|
||||
new_unique_id,
|
||||
)
|
||||
|
||||
_LOGGER.debug("Migration completed")
|
||||
|
||||
|
||||
async def _async_deflection_entities_list(
|
||||
avm_wrapper: AvmWrapper, device_friendly_name: str
|
||||
@@ -125,35 +221,7 @@ async def _async_wifi_entities_list(
|
||||
#
|
||||
# https://avm.de/fileadmin/user_upload/Global/Service/Schnittstellen/wlanconfigSCPD.pdf
|
||||
#
|
||||
wifi_count = len(
|
||||
[
|
||||
s
|
||||
for s in avm_wrapper.connection.services
|
||||
if s.startswith("WLANConfiguration")
|
||||
]
|
||||
)
|
||||
_LOGGER.debug("WiFi networks count: %s", wifi_count)
|
||||
networks: dict[int, dict[str, Any]] = {}
|
||||
for i in range(1, wifi_count + 1):
|
||||
network_info = await avm_wrapper.async_get_wlan_configuration(i)
|
||||
# Devices with 4 WLAN services, use the 2nd for internal communications
|
||||
if not (wifi_count == 4 and i == 2):
|
||||
networks[i] = network_info
|
||||
for i, network in networks.copy().items():
|
||||
networks[i]["switch_name"] = network["NewSSID"]
|
||||
if (
|
||||
len(
|
||||
[
|
||||
j
|
||||
for j, n in networks.items()
|
||||
if slugify(n["NewSSID"]) == slugify(network["NewSSID"])
|
||||
]
|
||||
)
|
||||
> 1
|
||||
):
|
||||
networks[i]["switch_name"] += f" ({WIFI_STANDARD[i]})"
|
||||
|
||||
_LOGGER.debug("WiFi networks list: %s", networks)
|
||||
networks = await _get_wifi_networks_list(avm_wrapper)
|
||||
return [
|
||||
FritzBoxWifiSwitch(avm_wrapper, device_friendly_name, index, data)
|
||||
for index, data in networks.items()
|
||||
@@ -225,6 +293,8 @@ async def async_setup_entry(
|
||||
|
||||
local_ip = await async_get_source_ip(avm_wrapper.hass, target_ip=avm_wrapper.host)
|
||||
|
||||
await _migrate_to_new_unique_id(hass, avm_wrapper)
|
||||
|
||||
entities_list = await async_all_entities_list(
|
||||
avm_wrapper,
|
||||
entry.title,
|
||||
@@ -554,8 +624,11 @@ class FritzBoxWifiSwitch(FritzBoxBaseSwitch):
|
||||
)
|
||||
self._network_num = network_num
|
||||
|
||||
description = f"Wi-Fi {network_data['switch_name']}"
|
||||
self._attr_translation_key = slugify(description)
|
||||
|
||||
switch_info = SwitchInfo(
|
||||
description=f"Wi-Fi {network_data['switch_name']}",
|
||||
description=description,
|
||||
friendly_name=device_friendly_name,
|
||||
icon="mdi:wifi",
|
||||
type=SWITCH_TYPE_WIFINETWORK,
|
||||
|
||||
@@ -106,6 +106,15 @@ class FritzConnectionMock:
|
||||
return action_data
|
||||
|
||||
|
||||
def wifi_services_with_ssids(ssid_1: str, ssid_2: str) -> dict[str, dict[str, Any]]:
|
||||
"""Return Fritz services with overridden Wi-Fi SSIDs."""
|
||||
services = deepcopy(MOCK_FB_SERVICES)
|
||||
for index, ssid in enumerate((ssid_1, ssid_2), start=1):
|
||||
services[f"WLANConfiguration{index}"]["GetInfo"]["NewSSID"] = ssid
|
||||
services[f"WLANConfiguration{index}"]["GetSSID"]["NewSSID"] = ssid
|
||||
return services
|
||||
|
||||
|
||||
@pytest.fixture(name="fc_data")
|
||||
def fc_data_mock() -> dict[str, dict[str, Any]]:
|
||||
"""Fixture for default fc_data."""
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_setup[fc_data0][switch.mock_title_wi_fi_wifi_2_4ghz-entry]
|
||||
# name: test_switch_setup[fc_data0][switch.mock_title_wi_fi_guest-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -115,7 +115,7 @@
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_wifi_2_4ghz',
|
||||
'entity_id': 'switch.mock_title_wi_fi_guest',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
@@ -123,36 +123,36 @@
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Mock Title Wi-Fi WiFi (2.4Ghz)',
|
||||
'object_id_base': 'Mock Title Wi-Fi Guest',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:wifi',
|
||||
'original_name': 'Mock Title Wi-Fi WiFi (2.4Ghz)',
|
||||
'original_name': 'Mock Title Wi-Fi Guest',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '1C:ED:6F:12:34:11-wi_fi_wifi_2_4ghz',
|
||||
'translation_key': 'wi_fi_guest',
|
||||
'unique_id': '1C:ED:6F:12:34:11-wi_fi_guest',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_setup[fc_data0][switch.mock_title_wi_fi_wifi_2_4ghz-state]
|
||||
# name: test_switch_setup[fc_data0][switch.mock_title_wi_fi_guest-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mock Title Wi-Fi WiFi (2.4Ghz)',
|
||||
'friendly_name': 'Mock Title Wi-Fi Guest',
|
||||
'icon': 'mdi:wifi',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_wifi_2_4ghz',
|
||||
'entity_id': 'switch.mock_title_wi_fi_guest',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_setup[fc_data0][switch.mock_title_wi_fi_wifi_5ghz-entry]
|
||||
# name: test_switch_setup[fc_data0][switch.mock_title_wi_fi_main_2_4ghz-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -166,7 +166,7 @@
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_wifi_5ghz',
|
||||
'entity_id': 'switch.mock_title_wi_fi_main_2_4ghz',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
@@ -174,29 +174,29 @@
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Mock Title Wi-Fi WiFi (5Ghz)',
|
||||
'object_id_base': 'Mock Title Wi-Fi Main 2.4Ghz',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:wifi',
|
||||
'original_name': 'Mock Title Wi-Fi WiFi (5Ghz)',
|
||||
'original_name': 'Mock Title Wi-Fi Main 2.4Ghz',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '1C:ED:6F:12:34:11-wi_fi_wifi_5ghz',
|
||||
'translation_key': 'wi_fi_main_2_4ghz',
|
||||
'unique_id': '1C:ED:6F:12:34:11-wi_fi_main_2_4ghz',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_setup[fc_data0][switch.mock_title_wi_fi_wifi_5ghz-state]
|
||||
# name: test_switch_setup[fc_data0][switch.mock_title_wi_fi_main_2_4ghz-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mock Title Wi-Fi WiFi (5Ghz)',
|
||||
'friendly_name': 'Mock Title Wi-Fi Main 2.4Ghz',
|
||||
'icon': 'mdi:wifi',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_wifi_5ghz',
|
||||
'entity_id': 'switch.mock_title_wi_fi_main_2_4ghz',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
@@ -355,7 +355,7 @@
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_setup[fc_data1][switch.mock_title_wi_fi_wifi-entry]
|
||||
# name: test_switch_setup[fc_data1][switch.mock_title_wi_fi_guest-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -369,7 +369,7 @@
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_wifi',
|
||||
'entity_id': 'switch.mock_title_wi_fi_guest',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
@@ -377,36 +377,36 @@
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Mock Title Wi-Fi WiFi',
|
||||
'object_id_base': 'Mock Title Wi-Fi Guest',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:wifi',
|
||||
'original_name': 'Mock Title Wi-Fi WiFi',
|
||||
'original_name': 'Mock Title Wi-Fi Guest',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '1C:ED:6F:12:34:11-wi_fi_wifi',
|
||||
'translation_key': 'wi_fi_guest',
|
||||
'unique_id': '1C:ED:6F:12:34:11-wi_fi_guest',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_setup[fc_data1][switch.mock_title_wi_fi_wifi-state]
|
||||
# name: test_switch_setup[fc_data1][switch.mock_title_wi_fi_guest-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mock Title Wi-Fi WiFi',
|
||||
'friendly_name': 'Mock Title Wi-Fi Guest',
|
||||
'icon': 'mdi:wifi',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_wifi',
|
||||
'entity_id': 'switch.mock_title_wi_fi_guest',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_setup[fc_data1][switch.mock_title_wi_fi_wifi2-entry]
|
||||
# name: test_switch_setup[fc_data1][switch.mock_title_wi_fi_main_2_4ghz-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -420,7 +420,7 @@
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_wifi2',
|
||||
'entity_id': 'switch.mock_title_wi_fi_main_2_4ghz',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
@@ -428,29 +428,29 @@
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Mock Title Wi-Fi WiFi2',
|
||||
'object_id_base': 'Mock Title Wi-Fi Main 2.4Ghz',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:wifi',
|
||||
'original_name': 'Mock Title Wi-Fi WiFi2',
|
||||
'original_name': 'Mock Title Wi-Fi Main 2.4Ghz',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '1C:ED:6F:12:34:11-wi_fi_wifi2',
|
||||
'translation_key': 'wi_fi_main_2_4ghz',
|
||||
'unique_id': '1C:ED:6F:12:34:11-wi_fi_main_2_4ghz',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_setup[fc_data1][switch.mock_title_wi_fi_wifi2-state]
|
||||
# name: test_switch_setup[fc_data1][switch.mock_title_wi_fi_main_2_4ghz-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mock Title Wi-Fi WiFi2',
|
||||
'friendly_name': 'Mock Title Wi-Fi Main 2.4Ghz',
|
||||
'icon': 'mdi:wifi',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_wifi2',
|
||||
'entity_id': 'switch.mock_title_wi_fi_main_2_4ghz',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
@@ -609,7 +609,7 @@
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_setup[fc_data2][switch.mock_title_wi_fi_wifi_2_4ghz-entry]
|
||||
# name: test_switch_setup[fc_data2][switch.mock_title_wi_fi_guest-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -623,7 +623,7 @@
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_wifi_2_4ghz',
|
||||
'entity_id': 'switch.mock_title_wi_fi_guest',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
@@ -631,36 +631,36 @@
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Mock Title Wi-Fi WiFi (2.4Ghz)',
|
||||
'object_id_base': 'Mock Title Wi-Fi Guest',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:wifi',
|
||||
'original_name': 'Mock Title Wi-Fi WiFi (2.4Ghz)',
|
||||
'original_name': 'Mock Title Wi-Fi Guest',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '1C:ED:6F:12:34:11-wi_fi_wifi_2_4ghz',
|
||||
'translation_key': 'wi_fi_guest',
|
||||
'unique_id': '1C:ED:6F:12:34:11-wi_fi_guest',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_setup[fc_data2][switch.mock_title_wi_fi_wifi_2_4ghz-state]
|
||||
# name: test_switch_setup[fc_data2][switch.mock_title_wi_fi_guest-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mock Title Wi-Fi WiFi (2.4Ghz)',
|
||||
'friendly_name': 'Mock Title Wi-Fi Guest',
|
||||
'icon': 'mdi:wifi',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_wifi_2_4ghz',
|
||||
'entity_id': 'switch.mock_title_wi_fi_guest',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_setup[fc_data2][switch.mock_title_wi_fi_wifi_5ghz-entry]
|
||||
# name: test_switch_setup[fc_data2][switch.mock_title_wi_fi_main_2_4ghz-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -674,7 +674,7 @@
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_wifi_5ghz',
|
||||
'entity_id': 'switch.mock_title_wi_fi_main_2_4ghz',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
@@ -682,29 +682,29 @@
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Mock Title Wi-Fi WiFi+ (5Ghz)',
|
||||
'object_id_base': 'Mock Title Wi-Fi Main 2.4Ghz',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:wifi',
|
||||
'original_name': 'Mock Title Wi-Fi WiFi+ (5Ghz)',
|
||||
'original_name': 'Mock Title Wi-Fi Main 2.4Ghz',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '1C:ED:6F:12:34:11-wi_fi_wifi_5ghz',
|
||||
'translation_key': 'wi_fi_main_2_4ghz',
|
||||
'unique_id': '1C:ED:6F:12:34:11-wi_fi_main_2_4ghz',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_setup[fc_data2][switch.mock_title_wi_fi_wifi_5ghz-state]
|
||||
# name: test_switch_setup[fc_data2][switch.mock_title_wi_fi_main_2_4ghz-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mock Title Wi-Fi WiFi+ (5Ghz)',
|
||||
'friendly_name': 'Mock Title Wi-Fi Main 2.4Ghz',
|
||||
'icon': 'mdi:wifi',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_wifi_5ghz',
|
||||
'entity_id': 'switch.mock_title_wi_fi_main_2_4ghz',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
@@ -920,7 +920,7 @@
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_setup[fc_data3][switch.mock_title_wi_fi_guestwifi-entry]
|
||||
# name: test_switch_setup[fc_data3][switch.mock_title_wi_fi_guest-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -934,7 +934,7 @@
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_guestwifi',
|
||||
'entity_id': 'switch.mock_title_wi_fi_guest',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
@@ -942,36 +942,36 @@
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Mock Title Wi-Fi GuestWifi',
|
||||
'object_id_base': 'Mock Title Wi-Fi Guest',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:wifi',
|
||||
'original_name': 'Mock Title Wi-Fi GuestWifi',
|
||||
'original_name': 'Mock Title Wi-Fi Guest',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '1C:ED:6F:12:34:11-wi_fi_guestwifi',
|
||||
'translation_key': 'wi_fi_guest',
|
||||
'unique_id': '1C:ED:6F:12:34:11-wi_fi_guest',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_setup[fc_data3][switch.mock_title_wi_fi_guestwifi-state]
|
||||
# name: test_switch_setup[fc_data3][switch.mock_title_wi_fi_guest-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mock Title Wi-Fi GuestWifi',
|
||||
'friendly_name': 'Mock Title Wi-Fi Guest',
|
||||
'icon': 'mdi:wifi',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_guestwifi',
|
||||
'entity_id': 'switch.mock_title_wi_fi_guest',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_setup[fc_data3][switch.mock_title_wi_fi_mywifi-entry]
|
||||
# name: test_switch_setup[fc_data3][switch.mock_title_wi_fi_main_2_4ghz-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -985,7 +985,7 @@
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_mywifi',
|
||||
'entity_id': 'switch.mock_title_wi_fi_main_2_4ghz',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
@@ -993,29 +993,29 @@
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Mock Title Wi-Fi MyWifi',
|
||||
'object_id_base': 'Mock Title Wi-Fi Main 2.4Ghz',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:wifi',
|
||||
'original_name': 'Mock Title Wi-Fi MyWifi',
|
||||
'original_name': 'Mock Title Wi-Fi Main 2.4Ghz',
|
||||
'platform': 'fritz',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '1C:ED:6F:12:34:11-wi_fi_mywifi',
|
||||
'translation_key': 'wi_fi_main_2_4ghz',
|
||||
'unique_id': '1C:ED:6F:12:34:11-wi_fi_main_2_4ghz',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_setup[fc_data3][switch.mock_title_wi_fi_mywifi-state]
|
||||
# name: test_switch_setup[fc_data3][switch.mock_title_wi_fi_main_2_4ghz-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mock Title Wi-Fi MyWifi',
|
||||
'friendly_name': 'Mock Title Wi-Fi Main 2.4Ghz',
|
||||
'icon': 'mdi:wifi',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.mock_title_wi_fi_mywifi',
|
||||
'entity_id': 'switch.mock_title_wi_fi_main_2_4ghz',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from copy import deepcopy
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from fritzconnection.core.exceptions import FritzActionError
|
||||
from fritzconnection.lib.fritzstatus import DefaultConnectionService
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.fritz import switch as fritz_switch
|
||||
from homeassistant.components.fritz.const import DOMAIN
|
||||
from homeassistant.components.switch import (
|
||||
DOMAIN as SWITCH_DOMAIN,
|
||||
@@ -25,13 +26,16 @@ from homeassistant.const import (
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.entity_registry import EntityRegistry
|
||||
from homeassistant.util import slugify
|
||||
|
||||
from .conftest import FritzConnectionMock
|
||||
from .conftest import FritzConnectionMock, wifi_services_with_ssids
|
||||
from .const import (
|
||||
MOCK_CALL_DEFLECTION_DATA,
|
||||
MOCK_FB_SERVICES,
|
||||
MOCK_HOST_ATTRIBUTES_DATA,
|
||||
MOCK_MESH_MASTER_MAC,
|
||||
MOCK_USER_DATA,
|
||||
)
|
||||
|
||||
@@ -397,7 +401,7 @@ async def test_switch_device_no_ip_address(
|
||||
STATE_ON,
|
||||
),
|
||||
(
|
||||
"switch.mock_title_wi_fi_mywifi",
|
||||
"switch.mock_title_wi_fi_guest",
|
||||
"async_set_wlan_configuration",
|
||||
STATE_ON,
|
||||
),
|
||||
@@ -455,3 +459,137 @@ async def test_switch_turn_on_off(
|
||||
|
||||
assert (state := hass.states.get(entity_id))
|
||||
assert state.state == state_value
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("ssid_1", "ssid_2", "old_descriptions", "new_identifiers"),
|
||||
[
|
||||
(
|
||||
"Main WiFi / +",
|
||||
"Guest WiFi / +",
|
||||
[
|
||||
"Wi-Fi Main WiFi / +",
|
||||
"Wi-Fi Guest WiFi / +",
|
||||
],
|
||||
["main_2_4ghz", "guest"],
|
||||
),
|
||||
(
|
||||
"My WiFi / +",
|
||||
"My WiFi / +",
|
||||
[
|
||||
"Wi-Fi My WiFi / + (2.4Ghz)",
|
||||
"Wi-Fi My WiFi / + (5Ghz)",
|
||||
],
|
||||
["main_2_4ghz", "guest"],
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_migrate_to_new_unique_id(
|
||||
hass: HomeAssistant,
|
||||
fc_class_mock,
|
||||
fh_class_mock,
|
||||
fs_class_mock,
|
||||
entity_registry: EntityRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
ssid_1: str,
|
||||
ssid_2: str,
|
||||
old_descriptions: list[str],
|
||||
new_identifiers: list[str],
|
||||
) -> None:
|
||||
"""Test migrate from old unique ids to new unique ids."""
|
||||
|
||||
MOCK_UNIQUE_ID = "1234567890"
|
||||
|
||||
fc_class_mock.return_value.override_services(
|
||||
wifi_services_with_ssids(ssid_1, ssid_2)
|
||||
)
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN, data=MOCK_USER_DATA, unique_id=MOCK_UNIQUE_ID
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
entity_ids: list[str] = []
|
||||
old_unique_ids: list[str] = []
|
||||
new_unique_ids: list[str] = []
|
||||
for old_description, new_identifier in zip(
|
||||
old_descriptions, new_identifiers, strict=True
|
||||
):
|
||||
old_unique_id = f"{MOCK_MESH_MASTER_MAC}-{slugify(old_description)}"
|
||||
new_unique_id = f"{MOCK_MESH_MASTER_MAC}-wi_fi_{new_identifier}"
|
||||
old_unique_ids.append(old_unique_id)
|
||||
new_unique_ids.append(new_unique_id)
|
||||
entity_ids.append(f"switch.fritz_{slugify(old_unique_id)}")
|
||||
|
||||
entity_registry.async_get_or_create(
|
||||
disabled_by=None,
|
||||
domain=SWITCH_DOMAIN,
|
||||
platform=DOMAIN,
|
||||
unique_id=old_unique_id,
|
||||
config_entry=entry,
|
||||
)
|
||||
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, MOCK_UNIQUE_ID)},
|
||||
connections={
|
||||
(dr.CONNECTION_NETWORK_MAC, MOCK_MESH_MASTER_MAC),
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for entity_id, old_unique_id in zip(entity_ids, old_unique_ids, strict=True):
|
||||
entity_entry = entity_registry.async_get(entity_id)
|
||||
assert entity_entry
|
||||
assert entity_entry.unique_id == old_unique_id
|
||||
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for entity_id, new_unique_id in zip(entity_ids, new_unique_ids, strict=True):
|
||||
entity_entry = entity_registry.async_get(entity_id)
|
||||
assert entity_entry
|
||||
assert entity_entry.unique_id == new_unique_id
|
||||
|
||||
|
||||
async def test_wifi_naming_internal_comm_and_skipped() -> None:
|
||||
"""Test skip internal Wi-Fi network."""
|
||||
# Prepare AvmWrapper mock with 4 Wi-Fi networks
|
||||
avm_wrapper = MagicMock()
|
||||
avm_wrapper.connection.services = [
|
||||
"WLANConfiguration1",
|
||||
"WLANConfiguration2",
|
||||
"WLANConfiguration3",
|
||||
"WLANConfiguration4",
|
||||
]
|
||||
# The 3rd network (index 2) should be skipped
|
||||
wifi_configs = [
|
||||
{"NewSSID": "wifi1"},
|
||||
{"NewSSID": "wifi2"},
|
||||
{"NewSSID": "wifi3"},
|
||||
{"NewSSID": "wifi4"},
|
||||
]
|
||||
avm_wrapper.async_get_wlan_configuration = AsyncMock(side_effect=wifi_configs)
|
||||
|
||||
networks = await fritz_switch._get_wifi_networks_list(avm_wrapper)
|
||||
# The 3rd network (index 2) should be skipped
|
||||
assert 3 not in networks # 1-based index, so 3 is the 3rd
|
||||
# The rest should be present
|
||||
assert set(networks.keys()) == {1, 2, 4}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("wifi_index", "wifi_count", "expected_name"),
|
||||
[
|
||||
(0, 2, "Main 2.4Ghz"),
|
||||
(1, 3, "Main 5Ghz"),
|
||||
(1, 2, "Guest"),
|
||||
(2, 4, None),
|
||||
(2, 5, None),
|
||||
],
|
||||
)
|
||||
def test_wifi_naming_helper(
|
||||
wifi_index: int, wifi_count: int, expected_name: str | None
|
||||
) -> None:
|
||||
"""Test Wi-Fi naming helper covers supported and fallback branches."""
|
||||
assert fritz_switch._wifi_naming({}, wifi_index, wifi_count) == expected_name
|
||||
|
||||
Reference in New Issue
Block a user