Make central AvmWrapper class fully async in Fritz!Tools (#83768)
parent
156c815499
commit
5fbc005224
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue