mirror of
https://github.com/home-assistant/core.git
synced 2026-09-27 01:46:11 -04:00
169 lines
6.0 KiB
Python
169 lines
6.0 KiB
Python
"""Support for image entities."""
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Any, override
|
|
|
|
from gardena_bluetooth.const import (
|
|
AquaContour,
|
|
AquaContourContours,
|
|
AquaContourPosition,
|
|
AquaContourWateringMode,
|
|
Spray,
|
|
)
|
|
from gardena_bluetooth.parse import CharacteristicContourPoints, ContourPoints
|
|
from gardena_bluetooth.utils import contour_to_svg
|
|
|
|
from homeassistant.components.image import ImageEntity, ImageEntityDescription
|
|
from homeassistant.const import EntityCategory
|
|
from homeassistant.core import HomeAssistant, callback
|
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
|
from homeassistant.util import dt as dt_util
|
|
|
|
from .coordinator import GardenaBluetoothConfigEntry, GardenaBluetoothCoordinator
|
|
from .entity import GardenaBluetoothDescriptorEntity, GardenaBluetoothEntity
|
|
|
|
CONTOURS: dict[int, CharacteristicContourPoints] = {
|
|
AquaContourWateringMode.CONTOUR_1: AquaContourContours.contour_points_1,
|
|
AquaContourWateringMode.CONTOUR_2: AquaContourContours.contour_points_2,
|
|
AquaContourWateringMode.CONTOUR_3: AquaContourContours.contour_points_3,
|
|
AquaContourWateringMode.CONTOUR_4: AquaContourContours.contour_points_4,
|
|
AquaContourWateringMode.CONTOUR_5: AquaContourContours.contour_points_5,
|
|
}
|
|
|
|
|
|
@dataclass(frozen=True, kw_only=True)
|
|
class GardenaBluetoothImageEntityDescription(ImageEntityDescription):
|
|
"""Description of entity."""
|
|
|
|
key: str = field(init=False)
|
|
translation_key: str = field(init=False, default="contour")
|
|
char: CharacteristicContourPoints
|
|
contour: int
|
|
|
|
def __post_init__(self):
|
|
"""Initialize calculated fields."""
|
|
object.__setattr__(self, "key", self.char.unique_id)
|
|
|
|
|
|
DESCRIPTIONS = tuple(
|
|
GardenaBluetoothImageEntityDescription(
|
|
char=char,
|
|
contour=contour,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
)
|
|
for contour, char in CONTOURS.items()
|
|
)
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
entry: GardenaBluetoothConfigEntry,
|
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
|
) -> None:
|
|
"""Set up image based on a config entry."""
|
|
coordinator = entry.runtime_data
|
|
entities: list[ImageEntity] = [
|
|
GardenaBluetoothContourImage(coordinator, description)
|
|
for description in DESCRIPTIONS
|
|
if description.char.unique_id in coordinator.characteristics
|
|
]
|
|
if GardenaBluetoothActiveContourImage.characteristics.issubset(
|
|
coordinator.characteristics
|
|
):
|
|
entities.append(GardenaBluetoothActiveContourImage(coordinator))
|
|
async_add_entities(entities)
|
|
|
|
|
|
class GardenaBluetoothImage(GardenaBluetoothEntity, ImageEntity):
|
|
"""Base for an image rendered from cached contour data."""
|
|
|
|
_attr_content_type = "image/svg+xml"
|
|
_image: bytes | None = None
|
|
|
|
def __init__(
|
|
self, coordinator: GardenaBluetoothCoordinator, context: Any = None
|
|
) -> None:
|
|
"""Initialize the image."""
|
|
super().__init__(coordinator, context)
|
|
# The coordinator entity does not chain its init, so image setup is missed.
|
|
ImageEntity.__init__(self, coordinator.hass)
|
|
|
|
def _render(self) -> bytes | None:
|
|
"""Render the current image."""
|
|
raise NotImplementedError
|
|
|
|
@callback
|
|
@override
|
|
def _handle_coordinator_update(self) -> None:
|
|
if (image := self._render()) != self._image:
|
|
self._image = image
|
|
self._attr_image_last_updated = dt_util.utcnow()
|
|
super()._handle_coordinator_update()
|
|
|
|
@override
|
|
async def async_image(self) -> bytes | None:
|
|
"""Return bytes of image."""
|
|
return self._image
|
|
|
|
|
|
class GardenaBluetoothContourImage(
|
|
GardenaBluetoothDescriptorEntity, GardenaBluetoothImage
|
|
):
|
|
"""Representation of a taught contour as a map."""
|
|
|
|
entity_description: GardenaBluetoothImageEntityDescription
|
|
|
|
def __init__(
|
|
self,
|
|
coordinator: GardenaBluetoothCoordinator,
|
|
description: GardenaBluetoothImageEntityDescription,
|
|
) -> None:
|
|
"""Initialize the contour image."""
|
|
super().__init__(coordinator, description, {description.char.unique_id})
|
|
self._attr_translation_placeholders = {"number": str(description.contour)}
|
|
|
|
@override
|
|
def _render(self) -> bytes | None:
|
|
return contour_to_svg(self.coordinator.get_cached(self.entity_description.char))
|
|
|
|
|
|
class GardenaBluetoothActiveContourImage(GardenaBluetoothImage):
|
|
"""Representation of the contour in use, with the current spray drawn in."""
|
|
|
|
_attr_translation_key = "active_contour"
|
|
|
|
characteristics = {
|
|
AquaContour.active_contour.unique_id,
|
|
AquaContourPosition.active_position.unique_id,
|
|
Spray.current_sector.unique_id,
|
|
Spray.current_distance.unique_id,
|
|
*(char.unique_id for char in CONTOURS.values()),
|
|
}
|
|
|
|
def __init__(self, coordinator: GardenaBluetoothCoordinator) -> None:
|
|
"""Initialize the active contour image."""
|
|
super().__init__(coordinator, self.characteristics)
|
|
self._attr_unique_id = f"{coordinator.address}-active_contour"
|
|
|
|
def _active_points(self) -> ContourPoints | None:
|
|
"""Points of the contour assigned to the position the sprinkler is at."""
|
|
position = self.coordinator.get_cached(AquaContourPosition.active_position)
|
|
assigned = self.coordinator.get_cached(AquaContour.active_contour) or []
|
|
if position is None or position < 1 or position > len(assigned):
|
|
return None
|
|
if (char := CONTOURS.get(assigned[position - 1])) is None:
|
|
return None
|
|
return self.coordinator.get_cached(char)
|
|
|
|
def _spray(self) -> tuple[int, int] | None:
|
|
"""Angle and distance the sprinkler is currently throwing at."""
|
|
angle = self.coordinator.get_cached(Spray.current_sector)
|
|
distance = self.coordinator.get_cached(Spray.current_distance)
|
|
if angle is None or not distance:
|
|
return None
|
|
return angle, distance
|
|
|
|
@override
|
|
def _render(self) -> bytes | None:
|
|
return contour_to_svg(self._active_points(), self._spray())
|