From 7e366a78e62725e415d371209d0e790503837289 Mon Sep 17 00:00:00 2001 From: Charles Garwood Date: Wed, 17 Aug 2022 02:36:56 -0400 Subject: [PATCH] Add Fully Kiosk Browser button platform (#76894) Co-authored-by: Franck Nijhof --- .../components/fully_kiosk/__init__.py | 2 +- .../components/fully_kiosk/button.py | 105 ++++++++++++++++++ tests/components/fully_kiosk/test_button.py | 69 ++++++++++++ 3 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/fully_kiosk/button.py create mode 100644 tests/components/fully_kiosk/test_button.py diff --git a/homeassistant/components/fully_kiosk/__init__.py b/homeassistant/components/fully_kiosk/__init__.py index a4c71168f4e..311dae20082 100644 --- a/homeassistant/components/fully_kiosk/__init__.py +++ b/homeassistant/components/fully_kiosk/__init__.py @@ -6,7 +6,7 @@ from homeassistant.core import HomeAssistant from .const import DOMAIN from .coordinator import FullyKioskDataUpdateCoordinator -PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] +PLATFORMS = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.SENSOR] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/fully_kiosk/button.py b/homeassistant/components/fully_kiosk/button.py new file mode 100644 index 00000000000..387ee638547 --- /dev/null +++ b/homeassistant/components/fully_kiosk/button.py @@ -0,0 +1,105 @@ +"""Fully Kiosk Browser button.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any + +from fullykiosk import FullyKiosk + +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 +from .coordinator import FullyKioskDataUpdateCoordinator +from .entity import FullyKioskEntity + + +@dataclass +class FullyButtonEntityDescriptionMixin: + """Mixin to describe a Fully Kiosk Browser button entity.""" + + press_action: Callable[[FullyKiosk], Any] + + +@dataclass +class FullyButtonEntityDescription( + ButtonEntityDescription, FullyButtonEntityDescriptionMixin +): + """Fully Kiosk Browser button description.""" + + +BUTTONS: tuple[FullyButtonEntityDescription, ...] = ( + FullyButtonEntityDescription( + key="restartApp", + name="Restart browser", + device_class=ButtonDeviceClass.RESTART, + entity_category=EntityCategory.CONFIG, + press_action=lambda fully: fully.restartApp(), + ), + FullyButtonEntityDescription( + key="rebootDevice", + name="Reboot device", + device_class=ButtonDeviceClass.RESTART, + entity_category=EntityCategory.CONFIG, + press_action=lambda fully: fully.rebootDevice(), + ), + FullyButtonEntityDescription( + key="toForeground", + name="Bring to foreground", + press_action=lambda fully: fully.toForeground(), + ), + FullyButtonEntityDescription( + key="toBackground", + name="Send to background", + press_action=lambda fully: fully.toBackground(), + ), + FullyButtonEntityDescription( + key="loadStartUrl", + name="Load start URL", + press_action=lambda fully: fully.loadStartUrl(), + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the Fully Kiosk Browser button entities.""" + coordinator: FullyKioskDataUpdateCoordinator = hass.data[DOMAIN][ + config_entry.entry_id + ] + + async_add_entities( + FullyButtonEntity(coordinator, description) for description in BUTTONS + ) + + +class FullyButtonEntity(FullyKioskEntity, ButtonEntity): + """Representation of a Fully Kiosk Browser button.""" + + entity_description: FullyButtonEntityDescription + + def __init__( + self, + coordinator: FullyKioskDataUpdateCoordinator, + description: FullyButtonEntityDescription, + ) -> None: + """Initialize the button.""" + super().__init__(coordinator) + self.entity_description = description + self._attr_unique_id = f"{coordinator.data['deviceID']}-{description.key}" + + async def async_press(self) -> None: + """Set the value of the entity.""" + await self.entity_description.press_action(self.coordinator.fully) + await self.coordinator.async_refresh() diff --git a/tests/components/fully_kiosk/test_button.py b/tests/components/fully_kiosk/test_button.py new file mode 100644 index 00000000000..7183fc3db92 --- /dev/null +++ b/tests/components/fully_kiosk/test_button.py @@ -0,0 +1,69 @@ +"""Test the Fully Kiosk Browser buttons.""" +from unittest.mock import MagicMock + +import homeassistant.components.button as button +from homeassistant.components.fully_kiosk.const import DOMAIN +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr, entity_registry as er + +from tests.common import MockConfigEntry + + +async def test_binary_sensors( + hass: HomeAssistant, + mock_fully_kiosk: MagicMock, + init_integration: MockConfigEntry, +) -> None: + """Test standard Fully Kiosk binary sensors.""" + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) + + entry = entity_registry.async_get("button.amazon_fire_restart_browser") + assert entry + assert entry.unique_id == "abcdef-123456-restartApp" + await call_service(hass, "press", "button.amazon_fire_restart_browser") + assert len(mock_fully_kiosk.restartApp.mock_calls) == 1 + + entry = entity_registry.async_get("button.amazon_fire_reboot_device") + assert entry + assert entry.unique_id == "abcdef-123456-rebootDevice" + await call_service(hass, "press", "button.amazon_fire_reboot_device") + assert len(mock_fully_kiosk.rebootDevice.mock_calls) == 1 + + entry = entity_registry.async_get("button.amazon_fire_bring_to_foreground") + assert entry + assert entry.unique_id == "abcdef-123456-toForeground" + await call_service(hass, "press", "button.amazon_fire_bring_to_foreground") + assert len(mock_fully_kiosk.toForeground.mock_calls) == 1 + + entry = entity_registry.async_get("button.amazon_fire_send_to_background") + assert entry + assert entry.unique_id == "abcdef-123456-toBackground" + await call_service(hass, "press", "button.amazon_fire_send_to_background") + assert len(mock_fully_kiosk.toBackground.mock_calls) == 1 + + entry = entity_registry.async_get("button.amazon_fire_load_start_url") + assert entry + assert entry.unique_id == "abcdef-123456-loadStartUrl" + await call_service(hass, "press", "button.amazon_fire_load_start_url") + assert len(mock_fully_kiosk.loadStartUrl.mock_calls) == 1 + + assert entry.device_id + device_entry = device_registry.async_get(entry.device_id) + assert device_entry + assert device_entry.configuration_url == "http://192.168.1.234:2323" + assert device_entry.entry_type is None + assert device_entry.hw_version is None + assert device_entry.identifiers == {(DOMAIN, "abcdef-123456")} + assert device_entry.manufacturer == "amzn" + assert device_entry.model == "KFDOWI" + assert device_entry.name == "Amazon Fire" + assert device_entry.sw_version == "1.42.5" + + +def call_service(hass, service, entity_id): + """Call any service on entity.""" + return hass.services.async_call( + button.DOMAIN, service, {ATTR_ENTITY_ID: entity_id}, blocking=True + )