From 105ad861bd231b5a76b8db665d857699c92f4ec6 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Thu, 16 Dec 2021 13:25:06 +0100 Subject: [PATCH] Add buttons and deprecate services for Fritz (#61483) * Add buttons and deprecate services * Exclude tests * Log full service name --- .coveragerc | 1 + homeassistant/components/fritz/button.py | 100 +++++++++++++++++++++++ homeassistant/components/fritz/common.py | 27 +++++- homeassistant/components/fritz/const.py | 1 + 4 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/fritz/button.py diff --git a/.coveragerc b/.coveragerc index a1250c86bda..7a13c433b83 100644 --- a/.coveragerc +++ b/.coveragerc @@ -371,6 +371,7 @@ omit = homeassistant/components/freebox/switch.py homeassistant/components/fritz/__init__.py homeassistant/components/fritz/binary_sensor.py + homeassistant/components/fritz/button.py homeassistant/components/fritz/common.py homeassistant/components/fritz/const.py homeassistant/components/fritz/device_tracker.py diff --git a/homeassistant/components/fritz/button.py b/homeassistant/components/fritz/button.py new file mode 100644 index 00000000000..ea807545323 --- /dev/null +++ b/homeassistant/components/fritz/button.py @@ -0,0 +1,100 @@ +"""Switches for AVM Fritz!Box buttons.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +import logging +from typing import Final + +from homeassistant.components.button import ( + ButtonDeviceClass, + ButtonEntity, + ButtonEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ENTITY_CATEGORY_CONFIG +from homeassistant.core import HomeAssistant +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.util import slugify + +from .common import FritzBoxTools +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +@dataclass +class FritzButtonDescriptionMixin: + """Mixin to describe a Button entity.""" + + press_action: Callable + + +@dataclass +class FritzButtonDescription(ButtonEntityDescription, FritzButtonDescriptionMixin): + """Class to describe a Button entity.""" + + +BUTTONS: Final = [ + FritzButtonDescription( + key="firmware_update", + name="Firmware Update", + device_class=ButtonDeviceClass.UPDATE, + entity_category=ENTITY_CATEGORY_CONFIG, + press_action=lambda router: router.async_trigger_firmware_update(), + ), + FritzButtonDescription( + key="reboot", + name="Reboot", + device_class=ButtonDeviceClass.RESTART, + entity_category=ENTITY_CATEGORY_CONFIG, + press_action=lambda router: router.async_trigger_reboot(), + ), + FritzButtonDescription( + key="reconnect", + name="Reconnect", + device_class=ButtonDeviceClass.RESTART, + entity_category=ENTITY_CATEGORY_CONFIG, + press_action=lambda router: router.async_trigger_reconnect(), + ), +] + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set buttons for device.""" + _LOGGER.debug("Setting up buttons") + router: FritzBoxTools = hass.data[DOMAIN][entry.entry_id] + + async_add_entities([FritzButton(router, entry.title, button) for button in BUTTONS]) + + +class FritzButton(ButtonEntity): + """Defines a Fritz!Box base button.""" + + entity_description: FritzButtonDescription + + def __init__( + self, + router: FritzBoxTools, + device_friendly_name: str, + description: FritzButtonDescription, + ) -> None: + """Initialize Fritz!Box button.""" + self.entity_description = description + self.router = router + + self._attr_name = f"{device_friendly_name} {description.name}" + self._attr_unique_id = slugify(self._attr_name) + self._attr_device_info = DeviceInfo( + connections={(CONNECTION_NETWORK_MAC, router.mac)} + ) + + async def async_press(self) -> None: + """Triggers Fritz!Box service.""" + await self.entity_description.press_action(self.router) diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index 4dca2dba4fc..6e5d89e58bd 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -6,7 +6,7 @@ from dataclasses import dataclass, field from datetime import datetime, timedelta import logging from types import MappingProxyType -from typing import Any, TypedDict +from typing import Any, TypedDict, cast from fritzconnection import FritzConnection from fritzconnection.core.exceptions import ( @@ -315,6 +315,25 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): _LOGGER.debug("Checking host info for FRITZ!Box router %s", self.host) self._update_available, self._latest_firmware = self._update_device_info() + async def async_trigger_firmware_update(self) -> bool: + """Trigger firmware update.""" + results = await self.hass.async_add_executor_job( + self.connection.call_action, "UserInterface:1", "X_AVM-DE_DoUpdate" + ) + return cast(bool, results["NewX_AVM-DE_UpdateState"]) + + async def async_trigger_reboot(self) -> None: + """Trigger device reboot.""" + await self.hass.async_add_executor_job( + self.connection.call_action, "DeviceConfig1", "Reboot" + ) + + async def async_trigger_reconnect(self) -> None: + """Trigger device reconnect.""" + await self.hass.async_add_executor_job( + self.connection.call_action, "WANIPConn1", "ForceTermination" + ) + async def service_fritzbox( self, service_call: ServiceCall, config_entry: ConfigEntry ) -> None: @@ -326,12 +345,18 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): try: if service_call.service == SERVICE_REBOOT: + _LOGGER.warning( + 'Service "fritz.reboot" is deprecated, please use the corresponding button entity instead' + ) await self.hass.async_add_executor_job( self.connection.call_action, "DeviceConfig1", "Reboot" ) return if service_call.service == SERVICE_RECONNECT: + _LOGGER.warning( + 'Service "fritz.reconnect" is deprecated, please use the corresponding button entity instead' + ) await self.hass.async_add_executor_job( self.connection.call_action, "WANIPConn1", diff --git a/homeassistant/components/fritz/const.py b/homeassistant/components/fritz/const.py index 0196a43ec4e..7bf65a8566d 100644 --- a/homeassistant/components/fritz/const.py +++ b/homeassistant/components/fritz/const.py @@ -7,6 +7,7 @@ from homeassistant.const import Platform DOMAIN = "fritz" PLATFORMS = [ + Platform.BUTTON, Platform.BINARY_SENSOR, Platform.DEVICE_TRACKER, Platform.SENSOR,