diff --git a/.coveragerc b/.coveragerc index 81f2d17c671..6be3ac8cef6 100644 --- a/.coveragerc +++ b/.coveragerc @@ -335,6 +335,7 @@ omit = homeassistant/components/fritz/const.py homeassistant/components/fritz/device_tracker.py homeassistant/components/fritz/sensor.py + homeassistant/components/fritz/services.py homeassistant/components/fritzbox_callmonitor/__init__.py homeassistant/components/fritzbox_callmonitor/const.py homeassistant/components/fritzbox_callmonitor/base.py diff --git a/homeassistant/components/fritz/__init__.py b/homeassistant/components/fritz/__init__.py index 5cbaa23c1b5..bc762dadcb7 100644 --- a/homeassistant/components/fritz/__init__.py +++ b/homeassistant/components/fritz/__init__.py @@ -16,6 +16,7 @@ from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from .common import FritzBoxTools, FritzData from .const import DATA_FRITZ, DOMAIN, PLATFORMS +from .services import async_setup_services, async_unload_services _LOGGER = logging.getLogger(__name__) @@ -55,6 +56,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # Load the other platforms like switch hass.config_entries.async_setup_platforms(entry, PLATFORMS) + await async_setup_services(hass) + return True @@ -73,4 +76,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if unload_ok: hass.data[DOMAIN].pop(entry.entry_id) + await async_unload_services(hass) + return unload_ok diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index 3a6bf132fb6..2708293b327 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -8,10 +8,16 @@ from typing import Any # pylint: disable=import-error from fritzconnection import FritzConnection +from fritzconnection.core.exceptions import ( + FritzActionError, + FritzConnectionException, + FritzServiceError, +) from fritzconnection.lib.fritzhosts import FritzHosts from fritzconnection.lib.fritzstatus import FritzStatus from homeassistant.core import callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_track_time_interval @@ -22,6 +28,8 @@ from .const import ( DEFAULT_PORT, DEFAULT_USERNAME, DOMAIN, + SERVICE_REBOOT, + SERVICE_RECONNECT, TRACKER_SCAN_INTERVAL, ) @@ -157,6 +165,25 @@ class FritzBoxTools: if new_device: async_dispatcher_send(self.hass, self.signal_device_new) + async def service_fritzbox(self, service: str) -> None: + """Define FRITZ!Box services.""" + _LOGGER.debug("FRITZ!Box router: %s", service) + try: + if service == SERVICE_REBOOT: + await self.hass.async_add_executor_job( + self.connection.call_action, "DeviceConfig1", "Reboot" + ) + elif service == SERVICE_RECONNECT: + await self.hass.async_add_executor_job( + self.connection.call_action, + "WANIPConn1", + "ForceTermination", + ) + except (FritzServiceError, FritzActionError) as ex: + raise HomeAssistantError("Service or parameter unknown") from ex + except FritzConnectionException as ex: + raise HomeAssistantError("Service not supported") from ex + class FritzData: """Storage class for platform global data.""" diff --git a/homeassistant/components/fritz/const.py b/homeassistant/components/fritz/const.py index 0bc33786a0f..dafe9f4d126 100644 --- a/homeassistant/components/fritz/const.py +++ b/homeassistant/components/fritz/const.py @@ -11,11 +11,14 @@ DEFAULT_HOST = "192.168.178.1" DEFAULT_PORT = 49000 DEFAULT_USERNAME = "" - ERROR_AUTH_INVALID = "invalid_auth" ERROR_CONNECTION_ERROR = "connection_error" ERROR_UNKNOWN = "unknown_error" +FRITZ_SERVICES = "fritz_services" +SERVICE_REBOOT = "reboot" +SERVICE_RECONNECT = "reconnect" + TRACKER_SCAN_INTERVAL = 30 UPTIME_DEVIATION = 5 diff --git a/homeassistant/components/fritz/services.py b/homeassistant/components/fritz/services.py new file mode 100644 index 00000000000..197a138c36e --- /dev/null +++ b/homeassistant/components/fritz/services.py @@ -0,0 +1,62 @@ +"""Services for Fritz integration.""" +import logging + +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 + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_services(hass: HomeAssistant): + """Set up services for Fritz integration.""" + if hass.data.get(FRITZ_SERVICES, False): + return + + hass.data[FRITZ_SERVICES] = True + + async def async_call_fritz_service(service_call): + """Call correct Fritz service.""" + + if not ( + fritzbox_entry_ids := await _async_get_configured_fritz_tools( + hass, service_call + ) + ): + raise HomeAssistantError( + f"Failed to call service '{service_call.service}'. Config entry for target not found" + ) + + for entry in fritzbox_entry_ids: + _LOGGER.debug("Executing service %s", service_call.service) + fritz_tools = hass.data[DOMAIN].get(entry) + await fritz_tools.service_fritzbox(service_call.service) + + for service in [SERVICE_REBOOT, SERVICE_RECONNECT]: + hass.services.async_register(DOMAIN, service, async_call_fritz_service) + + +async def _async_get_configured_fritz_tools( + hass: HomeAssistant, service_call: ServiceCall +): + """Get FritzBoxTools class from config entry.""" + + return [ + entry_id + for entry_id in await async_extract_config_entry_ids(hass, service_call) + if hass.config_entries.async_get_entry(entry_id).domain == DOMAIN + ] + + +async def async_unload_services(hass: HomeAssistant): + """Unload services for Fritz integration.""" + + if not hass.data.get(FRITZ_SERVICES): + return + + hass.data[FRITZ_SERVICES] = False + + hass.services.async_remove(DOMAIN, SERVICE_REBOOT) + hass.services.async_remove(DOMAIN, SERVICE_RECONNECT) diff --git a/homeassistant/components/fritz/services.yaml b/homeassistant/components/fritz/services.yaml new file mode 100644 index 00000000000..87b0e6fca71 --- /dev/null +++ b/homeassistant/components/fritz/services.yaml @@ -0,0 +1,13 @@ +reconnect: + description: Reconnects your FRITZ!Box internet connection + target: + entity: + integration: fritz + domain: binary_sensor + +reboot: + description: Reboots your FRITZ!Box + target: + entity: + integration: fritz + domain: binary_sensor