Make central AvmWrapper class fully async in Fritz!Tools (#83768)

pull/86042/head
Michael 2023-01-16 20:54:32 +01:00 committed by GitHub
parent 156c815499
commit 5fbc005224
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 108 additions and 155 deletions

View File

@ -34,7 +34,7 @@ from homeassistant.helpers import (
entity_registry as er,
update_coordinator,
)
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.util import dt as dt_util
@ -302,10 +302,12 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator[None]):
"""Event specific per FRITZ!Box entry to signal updates in devices."""
return f"{DOMAIN}-device-update-{self._unique_id}"
def _update_hosts_info(self) -> list[HostInfo]:
async def _async_update_hosts_info(self) -> list[HostInfo]:
"""Retrieve latest hosts information from the FRITZ!Box."""
try:
return self.fritz_hosts.get_hosts_info() # type: ignore [no-any-return]
return await self.hass.async_add_executor_job(
self.fritz_hosts.get_hosts_info
)
except Exception as ex: # pylint: disable=[broad-except]
if not self.hass.is_stopping:
raise HomeAssistantError("Error refreshing hosts info") from ex
@ -318,14 +320,22 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator[None]):
release_url = info.get("NewX_AVM-DE_InfoURL")
return bool(version), version, release_url
def _get_wan_access(self, ip_address: str) -> bool | None:
async def _async_update_device_info(self) -> tuple[bool, str | None, str | None]:
"""Retrieve latest device information from the FRITZ!Box."""
return await self.hass.async_add_executor_job(self._update_device_info)
async def _async_get_wan_access(self, ip_address: str) -> bool | None:
"""Get WAN access rule for given IP address."""
try:
return not self.connection.call_action(
"X_AVM-DE_HostFilter:1",
"GetWANAccessByIP",
NewIPv4Address=ip_address,
).get("NewDisallow")
wan_access = await self.hass.async_add_executor_job(
partial(
self.connection.call_action,
"X_AVM-DE_HostFilter:1",
"GetWANAccessByIP",
NewIPv4Address=ip_address,
)
)
return not wan_access.get("NewDisallow")
except FRITZ_EXCEPTIONS as ex:
_LOGGER.debug(
(
@ -337,10 +347,6 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator[None]):
)
return None
async def async_scan_devices(self, now: datetime | None = None) -> None:
"""Wrap up FritzboxTools class scan."""
await self.hass.async_add_executor_job(self.scan_devices, now)
def manage_device_info(
self, dev_info: Device, dev_mac: str, consider_home: bool
) -> bool:
@ -356,13 +362,13 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator[None]):
self._devices[dev_mac] = device
return True
def send_signal_device_update(self, new_device: bool) -> None:
async def async_send_signal_device_update(self, new_device: bool) -> None:
"""Signal device data updated."""
dispatcher_send(self.hass, self.signal_device_update)
async_dispatcher_send(self.hass, self.signal_device_update)
if new_device:
dispatcher_send(self.hass, self.signal_device_new)
async_dispatcher_send(self.hass, self.signal_device_new)
def scan_devices(self, now: datetime | None = None) -> None:
async def async_scan_devices(self, now: datetime | None = None) -> None:
"""Scan for new devices and return a list of found device ids."""
if self.hass.is_stopping:
@ -374,7 +380,7 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator[None]):
self._update_available,
self._latest_firmware,
self._release_url,
) = self._update_device_info()
) = await self._async_update_device_info()
_LOGGER.debug("Checking devices for FRITZ!Box device %s", self.host)
_default_consider_home = DEFAULT_CONSIDER_HOME.total_seconds()
@ -387,7 +393,7 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator[None]):
new_device = False
hosts = {}
for host in self._update_hosts_info():
for host in await self._async_update_hosts_info():
if not host.get("mac"):
continue
@ -411,14 +417,18 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator[None]):
self.mesh_role = MeshRoles.NONE
for mac, info in hosts.items():
if info.ip_address:
info.wan_access = self._get_wan_access(info.ip_address)
info.wan_access = await self._async_get_wan_access(info.ip_address)
if self.manage_device_info(info, mac, consider_home):
new_device = True
self.send_signal_device_update(new_device)
await self.async_send_signal_device_update(new_device)
return
try:
if not (topology := self.fritz_hosts.get_mesh_topology()):
if not (
topology := await self.hass.async_add_executor_job(
self.fritz_hosts.get_mesh_topology
)
):
raise Exception("Mesh supported but empty topology reported")
except FritzActionError:
self.mesh_role = MeshRoles.SLAVE
@ -457,7 +467,9 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator[None]):
dev_info: Device = hosts[dev_mac]
if dev_info.ip_address:
dev_info.wan_access = self._get_wan_access(dev_info.ip_address)
dev_info.wan_access = await self._async_get_wan_access(
dev_info.ip_address
)
for link in interf["node_links"]:
intf = mesh_intf.get(link["node_interface_1_uid"])
@ -472,7 +484,7 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator[None]):
if self.manage_device_info(dev_info, dev_mac, consider_home):
new_device = True
self.send_signal_device_update(new_device)
await self.async_send_signal_device_update(new_device)
async def async_trigger_firmware_update(self) -> bool:
"""Trigger firmware update."""
@ -615,7 +627,7 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator[None]):
class AvmWrapper(FritzBoxTools):
"""Setup AVM wrapper for API calls."""
def _service_call_action(
async def _async_service_call(
self,
service_name: str,
service_suffix: str,
@ -632,10 +644,13 @@ class AvmWrapper(FritzBoxTools):
return {}
try:
result: dict = self.connection.call_action(
f"{service_name}:{service_suffix}",
action_name,
**kwargs,
result: dict = await self.hass.async_add_executor_job(
partial(
self.connection.call_action,
f"{service_name}:{service_suffix}",
action_name,
**kwargs,
)
)
return result
except FritzSecurityError:
@ -666,13 +681,15 @@ class AvmWrapper(FritzBoxTools):
async def async_get_upnp_configuration(self) -> dict[str, Any]:
"""Call X_AVM-DE_UPnP service."""
return await self.hass.async_add_executor_job(self.get_upnp_configuration)
return await self._async_service_call("X_AVM-DE_UPnP", "1", "GetInfo")
async def async_get_wan_link_properties(self) -> dict[str, Any]:
"""Call WANCommonInterfaceConfig service."""
return await self.hass.async_add_executor_job(
partial(self.get_wan_link_properties)
return await self._async_service_call(
"WANCommonInterfaceConfig",
"1",
"GetCommonLinkProperties",
)
async def async_ipv6_active(self) -> bool:
@ -703,34 +720,49 @@ class AvmWrapper(FritzBoxTools):
)
return connection_info
async def async_get_num_port_mapping(self, con_type: str) -> dict[str, Any]:
"""Call GetPortMappingNumberOfEntries action."""
return await self._async_service_call(
con_type, "1", "GetPortMappingNumberOfEntries"
)
async def async_get_port_mapping(self, con_type: str, index: int) -> dict[str, Any]:
"""Call GetGenericPortMappingEntry action."""
return await self.hass.async_add_executor_job(
partial(self.get_port_mapping, con_type, index)
return await self._async_service_call(
con_type, "1", "GetGenericPortMappingEntry", NewPortMappingIndex=index
)
async def async_get_wlan_configuration(self, index: int) -> dict[str, Any]:
"""Call WLANConfiguration service."""
return await self.hass.async_add_executor_job(
partial(self.get_wlan_configuration, index)
return await self._async_service_call(
"WLANConfiguration", str(index), "GetInfo"
)
async def async_get_ontel_num_deflections(self) -> dict[str, Any]:
"""Call GetNumberOfDeflections action from X_AVM-DE_OnTel service."""
return await self._async_service_call(
"X_AVM-DE_OnTel", "1", "GetNumberOfDeflections"
)
async def async_get_ontel_deflections(self) -> dict[str, Any]:
"""Call GetDeflections action from X_AVM-DE_OnTel service."""
return await self.hass.async_add_executor_job(
partial(self.get_ontel_deflections)
)
return await self._async_service_call("X_AVM-DE_OnTel", "1", "GetDeflections")
async def async_set_wlan_configuration(
self, index: int, turn_on: bool
) -> dict[str, Any]:
"""Call SetEnable action from WLANConfiguration service."""
return await self.hass.async_add_executor_job(
partial(self.set_wlan_configuration, index, turn_on)
return await self._async_service_call(
"WLANConfiguration",
str(index),
"SetEnable",
NewEnable="1" if turn_on else "0",
)
async def async_set_deflection_enable(
@ -738,94 +770,7 @@ class AvmWrapper(FritzBoxTools):
) -> dict[str, Any]:
"""Call SetDeflectionEnable service."""
return await self.hass.async_add_executor_job(
partial(self.set_deflection_enable, index, turn_on)
)
async def async_add_port_mapping(
self, con_type: str, port_mapping: Any
) -> dict[str, Any]:
"""Call AddPortMapping service."""
return await self.hass.async_add_executor_job(
partial(
self.add_port_mapping,
con_type,
port_mapping,
)
)
async def async_set_allow_wan_access(
self, ip_address: str, turn_on: bool
) -> dict[str, Any]:
"""Call X_AVM-DE_HostFilter service."""
return await self.hass.async_add_executor_job(
partial(self.set_allow_wan_access, ip_address, turn_on)
)
def get_upnp_configuration(self) -> dict[str, Any]:
"""Call X_AVM-DE_UPnP service."""
return self._service_call_action("X_AVM-DE_UPnP", "1", "GetInfo")
def get_ontel_num_deflections(self) -> dict[str, Any]:
"""Call GetNumberOfDeflections action from X_AVM-DE_OnTel service."""
return self._service_call_action(
"X_AVM-DE_OnTel", "1", "GetNumberOfDeflections"
)
def get_ontel_deflections(self) -> dict[str, Any]:
"""Call GetDeflections action from X_AVM-DE_OnTel service."""
return self._service_call_action("X_AVM-DE_OnTel", "1", "GetDeflections")
def get_default_connection(self) -> dict[str, Any]:
"""Call Layer3Forwarding service."""
return self._service_call_action(
"Layer3Forwarding", "1", "GetDefaultConnectionService"
)
def get_num_port_mapping(self, con_type: str) -> dict[str, Any]:
"""Call GetPortMappingNumberOfEntries action."""
return self._service_call_action(con_type, "1", "GetPortMappingNumberOfEntries")
def get_port_mapping(self, con_type: str, index: int) -> dict[str, Any]:
"""Call GetGenericPortMappingEntry action."""
return self._service_call_action(
con_type, "1", "GetGenericPortMappingEntry", NewPortMappingIndex=index
)
def get_wlan_configuration(self, index: int) -> dict[str, Any]:
"""Call WLANConfiguration service."""
return self._service_call_action("WLANConfiguration", str(index), "GetInfo")
def get_wan_link_properties(self) -> dict[str, Any]:
"""Call WANCommonInterfaceConfig service."""
return self._service_call_action(
"WANCommonInterfaceConfig", "1", "GetCommonLinkProperties"
)
def set_wlan_configuration(self, index: int, turn_on: bool) -> dict[str, Any]:
"""Call SetEnable action from WLANConfiguration service."""
return self._service_call_action(
"WLANConfiguration",
str(index),
"SetEnable",
NewEnable="1" if turn_on else "0",
)
def set_deflection_enable(self, index: int, turn_on: bool) -> dict[str, Any]:
"""Call SetDeflectionEnable service."""
return self._service_call_action(
return await self._async_service_call(
"X_AVM-DE_OnTel",
"1",
"SetDeflectionEnable",
@ -833,17 +778,24 @@ class AvmWrapper(FritzBoxTools):
NewEnable="1" if turn_on else "0",
)
def add_port_mapping(self, con_type: str, port_mapping: Any) -> dict[str, Any]:
async def async_add_port_mapping(
self, con_type: str, port_mapping: Any
) -> dict[str, Any]:
"""Call AddPortMapping service."""
return self._service_call_action(
con_type, "1", "AddPortMapping", **port_mapping
return await self._async_service_call(
con_type,
"1",
"AddPortMapping",
**port_mapping,
)
def set_allow_wan_access(self, ip_address: str, turn_on: bool) -> dict[str, Any]:
async def async_set_allow_wan_access(
self, ip_address: str, turn_on: bool
) -> dict[str, Any]:
"""Call X_AVM-DE_HostFilter service."""
return self._service_call_action(
return await self._async_service_call(
"X_AVM-DE_HostFilter",
"1",
"DisallowWANAccessByIP",

View File

@ -39,14 +39,14 @@ from .const import (
_LOGGER = logging.getLogger(__name__)
def deflection_entities_list(
async def _async_deflection_entities_list(
avm_wrapper: AvmWrapper, device_friendly_name: str
) -> list[FritzBoxDeflectionSwitch]:
"""Get list of deflection entities."""
_LOGGER.debug("Setting up %s switches", SWITCH_TYPE_DEFLECTION)
deflections_response = avm_wrapper.get_ontel_num_deflections()
deflections_response = await avm_wrapper.async_get_ontel_num_deflections()
if not deflections_response:
_LOGGER.debug("The FRITZ!Box has no %s options", SWITCH_TYPE_DEFLECTION)
return []
@ -61,7 +61,7 @@ def deflection_entities_list(
_LOGGER.debug("The FRITZ!Box has no %s options", SWITCH_TYPE_DEFLECTION)
return []
if not (deflection_list := avm_wrapper.get_ontel_deflections()):
if not (deflection_list := await avm_wrapper.async_get_ontel_deflections()):
return []
items = xmltodict.parse(deflection_list["NewDeflectionList"])["List"]["Item"]
@ -74,7 +74,7 @@ def deflection_entities_list(
]
def port_entities_list(
async def _async_port_entities_list(
avm_wrapper: AvmWrapper, device_friendly_name: str, local_ip: str
) -> list[FritzBoxPortSwitch]:
"""Get list of port forwarding entities."""
@ -86,7 +86,7 @@ def port_entities_list(
return []
# Query port forwardings and setup a switch for each forward for the current device
resp = avm_wrapper.get_num_port_mapping(avm_wrapper.device_conn_type)
resp = await avm_wrapper.async_get_num_port_mapping(avm_wrapper.device_conn_type)
if not resp:
_LOGGER.debug("The FRITZ!Box has no %s options", SWITCH_TYPE_DEFLECTION)
return []
@ -103,7 +103,9 @@ def port_entities_list(
for i in range(port_forwards_count):
portmap = avm_wrapper.get_port_mapping(avm_wrapper.device_conn_type, i)
portmap = await avm_wrapper.async_get_port_mapping(
avm_wrapper.device_conn_type, i
)
if not portmap:
_LOGGER.debug("The FRITZ!Box has no %s options", SWITCH_TYPE_DEFLECTION)
continue
@ -136,7 +138,7 @@ def port_entities_list(
return entities_list
def wifi_entities_list(
async def _async_wifi_entities_list(
avm_wrapper: AvmWrapper, device_friendly_name: str
) -> list[FritzBoxWifiSwitch]:
"""Get list of wifi entities."""
@ -155,9 +157,7 @@ def wifi_entities_list(
_LOGGER.debug("WiFi networks count: %s", wifi_count)
networks: dict = {}
for i in range(1, wifi_count + 1):
network_info = avm_wrapper.connection.call_action(
f"WLANConfiguration{i}", "GetInfo"
)
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] = {
@ -190,7 +190,7 @@ def wifi_entities_list(
]
def profile_entities_list(
async def _async_profile_entities_list(
avm_wrapper: AvmWrapper,
data_fritz: FritzData,
) -> list[FritzBoxProfileSwitch]:
@ -221,7 +221,7 @@ def profile_entities_list(
return new_profiles
def all_entities_list(
async def async_all_entities_list(
avm_wrapper: AvmWrapper,
device_friendly_name: str,
data_fritz: FritzData,
@ -233,10 +233,10 @@ def all_entities_list(
return []
return [
*deflection_entities_list(avm_wrapper, device_friendly_name),
*port_entities_list(avm_wrapper, device_friendly_name, local_ip),
*wifi_entities_list(avm_wrapper, device_friendly_name),
*profile_entities_list(avm_wrapper, data_fritz),
*await _async_deflection_entities_list(avm_wrapper, device_friendly_name),
*await _async_port_entities_list(avm_wrapper, device_friendly_name, local_ip),
*await _async_wifi_entities_list(avm_wrapper, device_friendly_name),
*await _async_profile_entities_list(avm_wrapper, data_fritz),
]
@ -252,8 +252,7 @@ async def async_setup_entry(
local_ip = await async_get_source_ip(avm_wrapper.hass, target_ip=avm_wrapper.host)
entities_list = await hass.async_add_executor_job(
all_entities_list,
entities_list = await async_all_entities_list(
avm_wrapper,
entry.title,
data_fritz,
@ -263,12 +262,14 @@ async def async_setup_entry(
async_add_entities(entities_list)
@callback
def update_avm_device() -> None:
async def async_update_avm_device() -> None:
"""Update the values of the AVM device."""
async_add_entities(profile_entities_list(avm_wrapper, data_fritz))
async_add_entities(await _async_profile_entities_list(avm_wrapper, data_fritz))
entry.async_on_unload(
async_dispatcher_connect(hass, avm_wrapper.signal_device_new, update_avm_device)
async_dispatcher_connect(
hass, avm_wrapper.signal_device_new, async_update_avm_device
)
)