From 823091e0cacdb0e4a2de0d1319138ea55008af41 Mon Sep 17 00:00:00 2001 From: Luke Date: Sat, 2 Nov 2024 14:53:57 -0400 Subject: [PATCH 1/2] Add POC for image --- custom_components/bermuda/const.py | 8 ++- custom_components/bermuda/image.py | 71 +++++++++++++++++++++++++ custom_components/bermuda/manifest.json | 2 +- 3 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 custom_components/bermuda/image.py diff --git a/custom_components/bermuda/const.py b/custom_components/bermuda/const.py index a9f59f2..a3d12d1 100644 --- a/custom_components/bermuda/const.py +++ b/custom_components/bermuda/const.py @@ -6,6 +6,8 @@ import logging from typing import Final +from homeassistant.const import Platform + from .log_spam_less import BermudaLogSpamLess NAME = "Bermuda BLE Trilateration" @@ -33,7 +35,11 @@ SWITCH = "switch" DEVICE_TRACKER = "device_tracker" # PLATFORMS = [BINARY_SENSOR, SENSOR, SWITCH] -PLATFORMS = [SENSOR, DEVICE_TRACKER] +PLATFORMS = [ + Platform.SENSOR, + Platform.DEVICE_TRACKER, + # Platform.IMAGE +] # Should probably retreive this from the component, but it's in "DOMAIN" *shrug* DOMAIN_PRIVATE_BLE_DEVICE = "private_ble_device" diff --git a/custom_components/bermuda/image.py b/custom_components/bermuda/image.py new file mode 100644 index 0000000..5d92e3e --- /dev/null +++ b/custom_components/bermuda/image.py @@ -0,0 +1,71 @@ +"""Image platform for Bermuda BLE Trilateration.""" + +from datetime import datetime +from io import BytesIO +from random import randint + +import homeassistant.util.dt as dt_util +from homeassistant.components.image import ImageEntity +from homeassistant.const import EntityCategory +from homeassistant.util import slugify +from PIL import Image, ImageDraw + +from .entity import BermudaGlobalEntity + +# async def async_setup_entry( +# hass: HomeAssistant, +# entry: config_entries.ConfigEntry, +# async_add_entities: AddEntitiesCallback, +# ) -> None: +# """Setup sensor platform.""" +# coordinator: BermudaDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] +# async_add_entities([BermudaImage(hass, coordinator, entry, "Main floor")]) + + +class BermudaImage(BermudaGlobalEntity, ImageEntity): + """Image entity for Bermuda.""" + + image_last_updated: datetime + + def __init__(self, hass, coordinator, config_entry, floor) -> None: + super().__init__(coordinator, config_entry) + ImageEntity.__init__(self, hass) + self.image_bytes = b"" + self.floor = floor + self._attr_entity_category = EntityCategory.DIAGNOSTIC + self._attr_image_last_updated = dt_util.utcnow() + + def _handle_coordinator_update(self) -> None: + # On every coordinator run - update the image. Maybe when this is actually implemented + self._attr_image_last_updated = dt_util.utcnow() + image = Image.new("RGB", (500, 500), "white") + draw = ImageDraw.Draw(image) + coordinates = [(20, 20), (100, 100)] + for x, y in coordinates: + radius = randint(10, 40) # noqa + draw.ellipse((x - 2, y - 2, x + 2, y + 2), fill="black") + + draw.ellipse((x - radius, y - radius, x + radius, y + radius), outline="black") + output = BytesIO() + image.save(output, format="PNG") + + # Get the bytes of the SVG image + self.image_bytes = output.getvalue() + super()._handle_coordinator_update() + + async def async_image(self) -> bytes | None: + """Return bytes of image.""" + return self.image_bytes + + @property + def unique_id(self): + """ + "Uniquely identify this sensor so that it gets stored in the entity_registry, + and can be maintained / renamed etc by the user. + """ + return f"BERMUDA_GLOBAL_MAP_{slugify(self.floor)}" + + @property + def name(self): + """Gets the name of the sensor.""" + return f"{self.floor} Map" diff --git a/custom_components/bermuda/manifest.json b/custom_components/bermuda/manifest.json index 0ecc7d6..4633e7a 100644 --- a/custom_components/bermuda/manifest.json +++ b/custom_components/bermuda/manifest.json @@ -14,6 +14,6 @@ "integration_type": "device", "iot_class": "calculated", "issue_tracker": "https://github.com/agittins/bermuda/issues", - "requirements": [], + "requirements": ["pillow"], "version": "0.0.0" } From 435a0c1f79278fb3ceeebd860684ad6b53fe6bfa Mon Sep 17 00:00:00 2001 From: Luke Date: Mon, 11 Nov 2024 16:13:43 -0500 Subject: [PATCH 2/2] Change to svg --- custom_components/bermuda/const.py | 2 +- custom_components/bermuda/image.py | 52 +++++++++++++++++------------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/custom_components/bermuda/const.py b/custom_components/bermuda/const.py index a3d12d1..4e502e9 100644 --- a/custom_components/bermuda/const.py +++ b/custom_components/bermuda/const.py @@ -38,7 +38,7 @@ PLATFORMS = [ Platform.SENSOR, Platform.DEVICE_TRACKER, - # Platform.IMAGE + # Platform.IMAGE ] # Should probably retreive this from the component, but it's in "DOMAIN" *shrug* diff --git a/custom_components/bermuda/image.py b/custom_components/bermuda/image.py index 5d92e3e..bfd03e9 100644 --- a/custom_components/bermuda/image.py +++ b/custom_components/bermuda/image.py @@ -1,30 +1,39 @@ """Image platform for Bermuda BLE Trilateration.""" from datetime import datetime -from io import BytesIO from random import randint +from typing import TYPE_CHECKING import homeassistant.util.dt as dt_util +import svgwrite +from homeassistant import config_entries from homeassistant.components.image import ImageEntity from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util import slugify -from PIL import Image, ImageDraw +from .const import DOMAIN from .entity import BermudaGlobalEntity -# async def async_setup_entry( -# hass: HomeAssistant, -# entry: config_entries.ConfigEntry, -# async_add_entities: AddEntitiesCallback, -# ) -> None: -# """Setup sensor platform.""" -# coordinator: BermudaDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] -# async_add_entities([BermudaImage(hass, coordinator, entry, "Main floor")]) +if TYPE_CHECKING: + from .coordinator import BermudaDataUpdateCoordinator + + +async def async_setup_entry( + hass: HomeAssistant, + entry: config_entries.ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Setup sensor platform.""" + coordinator: BermudaDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities([BermudaImage(hass, coordinator, entry, "Main floor")]) class BermudaImage(BermudaGlobalEntity, ImageEntity): """Image entity for Bermuda.""" + _attr_content_type = "image/svg+xml" image_last_updated: datetime def __init__(self, hass, coordinator, config_entry, floor) -> None: @@ -36,25 +45,24 @@ def __init__(self, hass, coordinator, config_entry, floor) -> None: self._attr_image_last_updated = dt_util.utcnow() def _handle_coordinator_update(self) -> None: - # On every coordinator run - update the image. Maybe when this is actually implemented - self._attr_image_last_updated = dt_util.utcnow() - image = Image.new("RGB", (500, 500), "white") - draw = ImageDraw.Draw(image) + dwg = svgwrite.Drawing(size=(500, 500)) + dwg.add(dwg.rect(insert=(0, 0), size=("100%", "100%"), fill="white")) + coordinates = [(20, 20), (100, 100)] for x, y in coordinates: - radius = randint(10, 40) # noqa - draw.ellipse((x - 2, y - 2, x + 2, y + 2), fill="black") + radius = randint(10, 40) # noqa: S311 + + dwg.add(dwg.circle(center=(x, y), r=2, fill="black")) - draw.ellipse((x - radius, y - radius, x + radius, y + radius), outline="black") - output = BytesIO() - image.save(output, format="PNG") + dwg.add(dwg.circle(center=(x, y), r=radius, stroke="black", fill="none")) - # Get the bytes of the SVG image - self.image_bytes = output.getvalue() + # Convert SVG drawing to bytes + self.image_bytes = dwg.tostring().encode() + self._attr_image_last_updated = dt_util.utcnow() super()._handle_coordinator_update() async def async_image(self) -> bytes | None: - """Return bytes of image.""" + """Return bytes of SVG image.""" return self.image_bytes @property