diff --git a/homeassistant/components/fully_kiosk/__init__.py b/homeassistant/components/fully_kiosk/__init__.py index 582ae23aea4..99b477c2989 100644 --- a/homeassistant/components/fully_kiosk/__init__.py +++ b/homeassistant/components/fully_kiosk/__init__.py @@ -14,6 +14,7 @@ PLATFORMS = [ Platform.BINARY_SENSOR, Platform.BUTTON, Platform.CAMERA, + Platform.IMAGE, Platform.MEDIA_PLAYER, Platform.NOTIFY, Platform.NUMBER, diff --git a/homeassistant/components/fully_kiosk/image.py b/homeassistant/components/fully_kiosk/image.py new file mode 100644 index 00000000000..fbf3481e38b --- /dev/null +++ b/homeassistant/components/fully_kiosk/image.py @@ -0,0 +1,74 @@ +"""Support for Fully Kiosk Browser image.""" + +from __future__ import annotations + +from collections.abc import Callable, Coroutine +from dataclasses import dataclass +from typing import Any + +from fullykiosk import FullyKiosk, FullyKioskError + +from homeassistant.components.image import ImageEntity, ImageEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.entity_platform import AddEntitiesCallback +import homeassistant.util.dt as dt_util + +from .const import DOMAIN +from .coordinator import FullyKioskDataUpdateCoordinator +from .entity import FullyKioskEntity + + +@dataclass(frozen=True, kw_only=True) +class FullyImageEntityDescription(ImageEntityDescription): + """Fully Kiosk Browser image entity description.""" + + image_fn: Callable[[FullyKiosk], Coroutine[Any, Any, bytes]] + + +IMAGES: tuple[FullyImageEntityDescription, ...] = ( + FullyImageEntityDescription( + key="screenshot", + translation_key="screenshot", + image_fn=lambda fully: fully.getScreenshot(), + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up the Fully Kiosk Browser image entities.""" + coordinator: FullyKioskDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + FullyImageEntity(coordinator, description) for description in IMAGES + ) + + +class FullyImageEntity(FullyKioskEntity, ImageEntity): + """Implement the image entity for Fully Kiosk Browser.""" + + entity_description: FullyImageEntityDescription + _attr_content_type = "image/png" + + def __init__( + self, + coordinator: FullyKioskDataUpdateCoordinator, + description: FullyImageEntityDescription, + ) -> None: + """Initialize the entity.""" + FullyKioskEntity.__init__(self, coordinator) + ImageEntity.__init__(self, coordinator.hass) + self.entity_description = description + self._attr_unique_id = f"{coordinator.data['deviceID']}-{description.key}" + + async def async_image(self) -> bytes | None: + """Return bytes of image.""" + try: + image_bytes = await self.entity_description.image_fn(self.coordinator.fully) + except FullyKioskError as err: + raise HomeAssistantError(err) from err + else: + self._attr_image_last_updated = dt_util.utcnow() + return image_bytes diff --git a/homeassistant/components/fully_kiosk/strings.json b/homeassistant/components/fully_kiosk/strings.json index c6fe65b8383..9c0049d3e5f 100644 --- a/homeassistant/components/fully_kiosk/strings.json +++ b/homeassistant/components/fully_kiosk/strings.json @@ -56,6 +56,11 @@ "name": "Load start URL" } }, + "image": { + "screenshot": { + "name": "Screenshot" + } + }, "notify": { "overlay_message": { "name": "Overlay message" diff --git a/tests/components/fully_kiosk/test_image.py b/tests/components/fully_kiosk/test_image.py new file mode 100644 index 00000000000..0dda707037f --- /dev/null +++ b/tests/components/fully_kiosk/test_image.py @@ -0,0 +1,42 @@ +"""Test the Fully Kiosk Browser image platform.""" + +from http import HTTPStatus +from unittest.mock import MagicMock + +from fullykiosk import FullyKioskError + +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from tests.common import MockConfigEntry +from tests.typing import ClientSessionGenerator + + +async def test_image( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + hass_client: ClientSessionGenerator, + mock_fully_kiosk: MagicMock, + init_integration: MockConfigEntry, +) -> None: + """Test the image entity.""" + entity_image = "image.amazon_fire_screenshot" + entity = hass.states.get(entity_image) + assert entity + assert entity.state == "unknown" + entry = entity_registry.async_get(entity_image) + assert entry + assert entry.unique_id == "abcdef-123456-screenshot" + + mock_fully_kiosk.getScreenshot.return_value = b"image_bytes" + client = await hass_client() + resp = await client.get(f"/api/image_proxy/{entity_image}") + assert resp.status == HTTPStatus.OK + assert resp.headers["Content-Type"] == "image/png" + assert await resp.read() == b"image_bytes" + assert mock_fully_kiosk.getScreenshot.call_count == 1 + + mock_fully_kiosk.getScreenshot.side_effect = FullyKioskError("error", "status") + client = await hass_client() + resp = await client.get(f"/api/image_proxy/{entity_image}") + assert resp.status == HTTPStatus.INTERNAL_SERVER_ERROR