1
mirror of https://github.com/home-assistant/core synced 2024-10-07 10:13:38 +02:00

Fritz clean device_tracker service (#56535)

This commit is contained in:
Simone Chemelli 2021-10-27 12:01:06 +02:00 committed by GitHub
parent 19eba5a3a0
commit f8af44cac2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 118 additions and 14 deletions

View File

@ -17,15 +17,27 @@ from fritzconnection.core.exceptions import (
from fritzconnection.lib.fritzhosts import FritzHosts
from fritzconnection.lib.fritzstatus import FritzStatus
from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER_DOMAIN
from homeassistant.components.device_tracker.const import (
CONF_CONSIDER_HOME,
DEFAULT_CONSIDER_HOME,
)
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.components.switch import DOMAIN as DEVICE_SWITCH_DOMAIN
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.device_registry import (
CONNECTION_NETWORK_MAC,
async_entries_for_config_entry,
async_get,
)
from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send
from homeassistant.helpers.entity import DeviceInfo, Entity
from homeassistant.helpers.entity_registry import (
EntityRegistry,
RegistryEntry,
async_entries_for_device,
)
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.util import dt as dt_util
@ -35,6 +47,7 @@ from .const import (
DEFAULT_PORT,
DEFAULT_USERNAME,
DOMAIN,
SERVICE_CLEANUP,
SERVICE_REBOOT,
SERVICE_RECONNECT,
TRACKER_SCAN_INTERVAL,
@ -70,6 +83,13 @@ def device_filter_out_from_trackers(
return bool(reason)
def _cleanup_entity_filter(device: RegistryEntry) -> bool:
"""Filter only relevant entities."""
return device.domain == DEVICE_TRACKER_DOMAIN or (
device.domain == DEVICE_SWITCH_DOMAIN and "_internet_access" in device.entity_id
)
class ClassSetupMissing(Exception):
"""Raised when a Class func is called before setup."""
@ -281,29 +301,83 @@ class FritzBoxTools:
_LOGGER.debug("Checking host info for FRITZ!Box router %s", self.host)
self._update_available, self._latest_firmware = self._update_device_info()
async def service_fritzbox(self, service: str) -> None:
async def service_fritzbox(
self, service_call: ServiceCall, config_entry: ConfigEntry
) -> None:
"""Define FRITZ!Box services."""
_LOGGER.debug("FRITZ!Box router: %s", service)
_LOGGER.debug("FRITZ!Box router: %s", service_call.service)
if not self.connection:
raise HomeAssistantError("Unable to establish a connection")
try:
if service == SERVICE_REBOOT:
if service_call.service == SERVICE_REBOOT:
await self.hass.async_add_executor_job(
self.connection.call_action, "DeviceConfig1", "Reboot"
)
elif service == SERVICE_RECONNECT:
return
if service_call.service == SERVICE_RECONNECT:
await self.hass.async_add_executor_job(
self.connection.call_action,
"WANIPConn1",
"ForceTermination",
)
return
if service_call.service == SERVICE_CLEANUP:
device_hosts_list: list = await self.hass.async_add_executor_job(
self.fritz_hosts.get_hosts_info
)
except (FritzServiceError, FritzActionError) as ex:
raise HomeAssistantError("Service or parameter unknown") from ex
except FritzConnectionException as ex:
raise HomeAssistantError("Service not supported") from ex
entity_reg: EntityRegistry = (
await self.hass.helpers.entity_registry.async_get_registry()
)
ha_entity_reg_list: list[
RegistryEntry
] = self.hass.helpers.entity_registry.async_entries_for_config_entry(
entity_reg, config_entry.entry_id
)
entities_removed: bool = False
device_hosts_macs = {device["mac"] for device in device_hosts_list}
for entry in ha_entity_reg_list:
if (
not _cleanup_entity_filter(entry)
or entry.unique_id.split("_")[0] in device_hosts_macs
):
continue
_LOGGER.info("Removing entity: %s", entry.name or entry.original_name)
entity_reg.async_remove(entry.entity_id)
entities_removed = True
if entities_removed:
self._async_remove_empty_devices(entity_reg, config_entry)
@callback
def _async_remove_empty_devices(
self, entity_reg: EntityRegistry, config_entry: ConfigEntry
) -> None:
"""Remove devices with no entities."""
device_reg = async_get(self.hass)
device_list = async_entries_for_config_entry(device_reg, config_entry.entry_id)
for device_entry in device_list:
if async_entries_for_device(
entity_reg,
device_entry.id,
include_disabled_entities=True,
):
_LOGGER.info("Removing device: %s", device_entry.name)
device_reg.async_remove_device(device_entry.id)
@dataclass
class FritzData:

View File

@ -22,6 +22,7 @@ ERROR_UNKNOWN = "unknown_error"
FRITZ_SERVICES = "fritz_services"
SERVICE_REBOOT = "reboot"
SERVICE_RECONNECT = "reconnect"
SERVICE_CLEANUP = "cleanup"
SWITCH_TYPE_DEFLECTION = "CallDeflection"
SWITCH_TYPE_PORTFORWARD = "PortForward"

View File

@ -5,15 +5,25 @@ from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.service import async_extract_config_entry_ids
from .const import DOMAIN, FRITZ_SERVICES, SERVICE_REBOOT, SERVICE_RECONNECT
from .common import FritzBoxTools
from .const import (
DOMAIN,
FRITZ_SERVICES,
SERVICE_CLEANUP,
SERVICE_REBOOT,
SERVICE_RECONNECT,
)
_LOGGER = logging.getLogger(__name__)
SERVICE_LIST = [SERVICE_CLEANUP, SERVICE_REBOOT, SERVICE_RECONNECT]
async def async_setup_services(hass: HomeAssistant) -> None:
"""Set up services for Fritz integration."""
for service in (SERVICE_REBOOT, SERVICE_RECONNECT):
for service in SERVICE_LIST:
if hass.services.has_service(DOMAIN, service):
return
@ -29,12 +39,18 @@ async def async_setup_services(hass: HomeAssistant) -> None:
f"Failed to call service '{service_call.service}'. Config entry for target not found"
)
for entry in fritzbox_entry_ids:
for entry_id in fritzbox_entry_ids:
_LOGGER.debug("Executing service %s", service_call.service)
fritz_tools = hass.data[DOMAIN][entry]
await fritz_tools.service_fritzbox(service_call.service)
fritz_tools: FritzBoxTools = hass.data[DOMAIN][entry_id]
if config_entry := hass.config_entries.async_get_entry(entry_id):
await fritz_tools.service_fritzbox(service_call, config_entry)
else:
_LOGGER.error(
"Executing service %s failed, no config entry found",
service_call.service,
)
for service in (SERVICE_REBOOT, SERVICE_RECONNECT):
for service in SERVICE_LIST:
hass.services.async_register(DOMAIN, service, async_call_fritz_service)
@ -59,5 +75,5 @@ async def async_unload_services(hass: HomeAssistant) -> None:
hass.data[FRITZ_SERVICES] = False
hass.services.async_remove(DOMAIN, SERVICE_REBOOT)
hass.services.async_remove(DOMAIN, SERVICE_RECONNECT)
for service in SERVICE_LIST:
hass.services.async_remove(DOMAIN, service)

View File

@ -22,3 +22,16 @@ reboot:
integration: fritz
entity:
device_class: connectivity
cleanup:
description: Remove FRITZ!Box stale device_tracker entities
fields:
device_id:
name: Fritz!Box Device
description: Select the Fritz!Box to check
required: true
selector:
device:
integration: fritz
entity:
device_class: connectivity