From a0f4e9ea283676274f6dda496e1bd804d1f32936 Mon Sep 17 00:00:00 2001 From: SCA075 <82227818+sca075@users.noreply.github.com> Date: Fri, 6 Dec 2024 19:11:20 +0100 Subject: [PATCH] Improving ObstacleImages View. - Reduced THRead from 3 to 1 for the image download. - Added more logging to check the CameraMode. - Added async immage load. - Safe Fail link creation using vacuum api Signed-off-by: 82227818+sca075@users.noreply.github.com <82227818+sca075@users.noreply.github.com> --- .../mqtt_vacuum_camera/camera.py | 47 +++++++++++++++---- .../mqtt_vacuum_camera/common.py | 10 +++- .../valetudo/hypfer/image_draw.py | 11 +++-- 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/custom_components/mqtt_vacuum_camera/camera.py b/custom_components/mqtt_vacuum_camera/camera.py index 23adcebf..331888ea 100755 --- a/custom_components/mqtt_vacuum_camera/camera.py +++ b/custom_components/mqtt_vacuum_camera/camera.py @@ -1,6 +1,6 @@ """ Camera -Version: v2024.12.0 +Version: v2024.12.1 """ from __future__ import annotations @@ -248,8 +248,9 @@ async def handle_obstacle_view(self, event): if self._shared.camera_mode == CameraModes.OBSTACLE_VIEW: self._shared.camera_mode = CameraModes.MAP_VIEW + _LOGGER.debug(f"Camera Mode Change to {self._shared.camera_mode}") self._should_poll = True - return + return await self.async_update() if ( self._shared.obstacles_data @@ -258,6 +259,7 @@ async def handle_obstacle_view(self, event): _LOGGER.debug(f"Received event: {event.event_type}, Data: {event.data}") if event.data.get("entity_id") == self.entity_id: self._shared.camera_mode = CameraModes.OBSTACLE_DOWNLOAD + _LOGGER.debug(f"Camera Mode Change to {self._shared.camera_mode}") self._should_poll = False # Turn off polling coordinates = event.data.get("coordinates") if coordinates: @@ -288,11 +290,14 @@ async def handle_obstacle_view(self, event): ) self._should_poll = True # Turn on polling self._shared.camera_mode = CameraModes.MAP_VIEW + _LOGGER.debug( + f"Camera Mode Change to {self._shared.camera_mode}" + ) return None if temp_image is not None: try: # Open the downloaded image with PIL - pil_img = Image.open(temp_image) + pil_img = await self.async_open_image(temp_image) # Resize the image if resize_to is provided pil_img.thumbnail((self._image_w, self._image_h)) @@ -304,6 +309,9 @@ async def handle_obstacle_view(self, event): f"{self._file_name}: Error processing image: {e}" ) self._shared.camera_mode = CameraModes.MAP_VIEW + _LOGGER.debug( + f"Camera Mode Change to {self._shared.camera_mode}" + ) self._should_poll = True # Turn on polling return None @@ -311,8 +319,14 @@ async def handle_obstacle_view(self, event): self.run_async_pil_to_bytes(pil_img) ) self._shared.camera_mode = CameraModes.OBSTACLE_VIEW + _LOGGER.debug( + f"Camera Mode Change to {self._shared.camera_mode}" + ) else: self._shared.camera_mode = CameraModes.MAP_VIEW + _LOGGER.debug( + f"Camera Mode Change to {self._shared.camera_mode}" + ) self._should_poll = True # Turn on polling else: _LOGGER.debug("No nearby obstacle found.") @@ -326,9 +340,9 @@ async def handle_obstacle_view(self, event): async def _async_find_nearest_obstacle(x, y, obstacles): """Find the nearest obstacle to the given coordinates.""" nearest_obstacle = None - min_distance = float("inf") # Start with a very large distance + min_distance = 500 # Start with a very large distance _LOGGER.debug( - f"Finding the nearest {min_distance} obstacle to coordinates: {x}, {y}" + f"Finding in the nearest {min_distance} piyels obstacle to coordinates: {x}, {y}" ) for obstacle in obstacles: @@ -345,8 +359,23 @@ async def _async_find_nearest_obstacle(x, y, obstacles): return nearest_obstacle - @staticmethod - async def download_image(url: str, storage_path: str, filename: str): + async def async_open_image(self, file_path) -> Image.Image: + """ + Asynchronously open an image file using a thread pool. + Args: + file_path (str): Path to the image file. + + Returns: + Image.Image: Opened PIL image. + """ + executor = ThreadPoolExecutor( + max_workers=1, thread_name_prefix=f"{self._file_name}_camera" + ) + loop = asyncio.get_running_loop() + pil_img = await loop.run_in_executor(executor, Image.open, file_path) + return pil_img + + async def download_image(self, url: str, storage_path: str, filename: str): """ Asynchronously download an image using threading to avoid blocking. @@ -384,7 +413,9 @@ async def blocking_download(): _LOGGER.error(f"Error downloading image: {e}") return None - executor = ThreadPoolExecutor(max_workers=3) # Limit to 3 workers + executor = ThreadPoolExecutor( + max_workers=1, thread_name_prefix=f"{self._file_name}_camera" + ) # Limit to 3 workers # Run the blocking I/O in a thread return await asyncio.get_running_loop().run_in_executor( diff --git a/custom_components/mqtt_vacuum_camera/common.py b/custom_components/mqtt_vacuum_camera/common.py index 9c644c35..3294c3d1 100755 --- a/custom_components/mqtt_vacuum_camera/common.py +++ b/custom_components/mqtt_vacuum_camera/common.py @@ -219,6 +219,12 @@ def compose_obstacle_links(vacuum_host_ip: str, obstacles: list) -> list: Compose JSON with obstacle details including the image link. """ obstacle_links = [] + if not obstacles or not vacuum_host_ip: + _LOGGER.debug( + f"Obstacle links: no obstacles: " + f"{obstacles} and / or ip: {vacuum_host_ip} to link." + ) + return None for obstacle in obstacles: # Extract obstacle details @@ -226,7 +232,7 @@ def compose_obstacle_links(vacuum_host_ip: str, obstacles: list) -> list: points = obstacle.get("points", {}) image_id = obstacle.get("id", "None") - if label and points and image_id: + if label and points and image_id and vacuum_host_ip: # Append formatted obstacle data if image_id != "None": # Compose the link @@ -246,6 +252,6 @@ def compose_obstacle_links(vacuum_host_ip: str, obstacles: list) -> list: } ) - _LOGGER.debug(f"Obstacle links: {obstacle_links}") + _LOGGER.debug(f"Obstacle links: linked data complete.") return obstacle_links diff --git a/custom_components/mqtt_vacuum_camera/valetudo/hypfer/image_draw.py b/custom_components/mqtt_vacuum_camera/valetudo/hypfer/image_draw.py index 79014faa..7a14dc27 100755 --- a/custom_components/mqtt_vacuum_camera/valetudo/hypfer/image_draw.py +++ b/custom_components/mqtt_vacuum_camera/valetudo/hypfer/image_draw.py @@ -122,9 +122,14 @@ async def async_draw_obstacle( obstacle_objects.append(obstacle_obj) # Store obstacle data in shared data - self.img_h.shared.obstacles_data = compose_obstacle_links( - self.img_h.shared.vacuum_ips, obstacle_objects - ) + if self.img_h.shared.vacuum_ips: + self.img_h.shared.obstacles_data = compose_obstacle_links( + self.img_h.shared.vacuum_ips, obstacle_objects + ) + elif self.img_h.shared.vacuum_api: + self.img_h.shared.obstacles_data = compose_obstacle_links( + self.img_h.shared.vacuum_api.spit("http://")[1], obstacle_objects + ) # Draw obstacles on the map if obstacle_objects: