From abe78b1212602d8b19562d6acc0adf9361302327 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Sat, 14 May 2022 02:24:29 +0200 Subject: [PATCH] Add QNAP QSW Button platform (#70980) Co-authored-by: J. Nick Koston --- homeassistant/components/qnap_qsw/__init__.py | 2 +- homeassistant/components/qnap_qsw/button.py | 77 +++++++++++++++++++ homeassistant/components/qnap_qsw/const.py | 1 + tests/components/qnap_qsw/test_button.py | 37 +++++++++ tests/components/qnap_qsw/util.py | 6 ++ 5 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/qnap_qsw/button.py create mode 100644 tests/components/qnap_qsw/test_button.py diff --git a/homeassistant/components/qnap_qsw/__init__.py b/homeassistant/components/qnap_qsw/__init__.py index 838a567199d..26ed8066686 100644 --- a/homeassistant/components/qnap_qsw/__init__.py +++ b/homeassistant/components/qnap_qsw/__init__.py @@ -11,7 +11,7 @@ from homeassistant.helpers import aiohttp_client from .const import DOMAIN from .coordinator import QswUpdateCoordinator -PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR] +PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.SENSOR] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/qnap_qsw/button.py b/homeassistant/components/qnap_qsw/button.py new file mode 100644 index 00000000000..1c13310fe05 --- /dev/null +++ b/homeassistant/components/qnap_qsw/button.py @@ -0,0 +1,77 @@ +"""Support for the QNAP QSW buttons.""" +from __future__ import annotations + +from collections.abc import Awaitable, Callable +from dataclasses import dataclass +from typing import Final + +from aioqsw.localapi import QnapQswApi + +from homeassistant.components.button import ( + ButtonDeviceClass, + ButtonEntity, + ButtonEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN, QSW_REBOOT +from .coordinator import QswUpdateCoordinator +from .entity import QswEntity + + +@dataclass +class QswButtonDescriptionMixin: + """Mixin to describe a Button entity.""" + + press_action: Callable[[QnapQswApi], Awaitable[bool]] + + +@dataclass +class QswButtonDescription(ButtonEntityDescription, QswButtonDescriptionMixin): + """Class to describe a Button entity.""" + + +BUTTON_TYPES: Final[tuple[QswButtonDescription, ...]] = ( + QswButtonDescription( + device_class=ButtonDeviceClass.RESTART, + entity_category=EntityCategory.CONFIG, + key=QSW_REBOOT, + name="Reboot", + press_action=lambda qsw: qsw.reboot(), + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Add QNAP QSW buttons from a config_entry.""" + coordinator: QswUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + QswButton(coordinator, description, entry) for description in BUTTON_TYPES + ) + + +class QswButton(QswEntity, ButtonEntity): + """Define a QNAP QSW button.""" + + entity_description: QswButtonDescription + + def __init__( + self, + coordinator: QswUpdateCoordinator, + description: QswButtonDescription, + entry: ConfigEntry, + ) -> None: + """Initialize.""" + super().__init__(coordinator, entry) + self._attr_name = f"{self.product} {description.name}" + self._attr_unique_id = f"{entry.unique_id}_{description.key}" + self.entity_description = description + + async def async_press(self) -> None: + """Triggers the QNAP QSW button action.""" + await self.entity_description.press_action(self.coordinator.qsw) diff --git a/homeassistant/components/qnap_qsw/const.py b/homeassistant/components/qnap_qsw/const.py index a6cacfd1c40..e583c0250f4 100644 --- a/homeassistant/components/qnap_qsw/const.py +++ b/homeassistant/components/qnap_qsw/const.py @@ -10,4 +10,5 @@ MANUFACTURER: Final = "QNAP" RPM: Final = "rpm" +QSW_REBOOT = "reboot" QSW_TIMEOUT_SEC: Final = 25 diff --git a/tests/components/qnap_qsw/test_button.py b/tests/components/qnap_qsw/test_button.py new file mode 100644 index 00000000000..5423c9686d4 --- /dev/null +++ b/tests/components/qnap_qsw/test_button.py @@ -0,0 +1,37 @@ +"""The sensor tests for the QNAP QSW platform.""" + +from unittest.mock import patch + +from homeassistant.components.button.const import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS +from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN +from homeassistant.core import HomeAssistant + +from .util import SYSTEM_COMMAND_MOCK, USERS_VERIFICATION_MOCK, async_init_integration + + +async def test_qnap_buttons(hass: HomeAssistant) -> None: + """Test buttons.""" + + await async_init_integration(hass) + + state = hass.states.get("button.qsw_m408_4c_reboot") + assert state + assert state.state == STATE_UNKNOWN + + with patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_users_verification", + return_value=USERS_VERIFICATION_MOCK, + ) as mock_users_verification, patch( + "homeassistant.components.qnap_qsw.QnapQswApi.post_system_command", + return_value=SYSTEM_COMMAND_MOCK, + ) as mock_post_system_command: + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: "button.qsw_m408_4c_reboot"}, + blocking=True, + ) + await hass.async_block_till_done() + + mock_users_verification.assert_called_once() + mock_post_system_command.assert_called_once() diff --git a/tests/components/qnap_qsw/util.py b/tests/components/qnap_qsw/util.py index 57b7b61a59d..28e7f7881d5 100644 --- a/tests/components/qnap_qsw/util.py +++ b/tests/components/qnap_qsw/util.py @@ -90,6 +90,12 @@ FIRMWARE_INFO_MOCK = { }, } +SYSTEM_COMMAND_MOCK = { + API_ERROR_CODE: 200, + API_ERROR_MESSAGE: "OK", + API_RESULT: "None", +} + SYSTEM_SENSOR_MOCK = { API_ERROR_CODE: 200, API_ERROR_MESSAGE: "OK",