From 8b425ede4c2604c793cfd18efa957d828c130a82 Mon Sep 17 00:00:00 2001 From: SCA075 <82227818+sca075@users.noreply.github.com> Date: Fri, 6 Dec 2024 18:55:53 +0100 Subject: [PATCH 1/5] adding checks to avoid None error when rooms or maps data are missing. Signed-off-by: 82227818+sca075@users.noreply.github.com <82227818+sca075@users.noreply.github.com> --- .../mqtt_vacuum_camera/coordinator.py | 69 ++++++++++--------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/custom_components/mqtt_vacuum_camera/coordinator.py b/custom_components/mqtt_vacuum_camera/coordinator.py index 7e03802d..76d91618 100644 --- a/custom_components/mqtt_vacuum_camera/coordinator.py +++ b/custom_components/mqtt_vacuum_camera/coordinator.py @@ -143,35 +143,42 @@ async def async_update_sensor_data(self, sensor_data): """ Update the sensor data format before sending to the sensors. """ - - if sensor_data: - # Assume sensor_data is a dictionary or transform it into the expected format - battery_level = await self.connector.get_battery_level() - vacuum_state = await self.connector.get_vacuum_status() - vacuum_room = self.shared.current_room - if not vacuum_room: - vacuum_room = {"in_room": "Unsupported"} - last_run_stats = sensor_data.get("last_run_stats", {}) - last_loaded_map = sensor_data.get("last_loaded_map", {}) - formatted_data = { - "mainBrush": sensor_data.get("mainBrush", 0), - "sideBrush": sensor_data.get("sideBrush", 0), - "filter": sensor_data.get("filter", 0), - "currentCleanTime": sensor_data.get("currentCleanTime", 0), - "currentCleanArea": sensor_data.get("currentCleanArea", 0), - "cleanTime": sensor_data.get("cleanTime", 0), - "cleanArea": sensor_data.get("cleanArea", 0), - "cleanCount": sensor_data.get("cleanCount", 0), - "battery": battery_level, - "state": vacuum_state, - "last_run_start": last_run_stats.get("startTime", 0), - "last_run_end": last_run_stats.get("endTime", 0), - "last_run_duration": last_run_stats.get("duration", 0), - "last_run_area": last_run_stats.get("area", 0), - "last_bin_out": sensor_data.get("last_bin_out", 0), - "last_bin_full": sensor_data.get("last_bin_full", 0), - "last_loaded_map": last_loaded_map.get("name", "NoMap"), - "robot_in_room": vacuum_room.get("in_room", "Unsupported"), - } - return formatted_data + try: + if sensor_data: + # Assume sensor_data is a dictionary or transform it into the expected format + battery_level = await self.connector.get_battery_level() + vacuum_state = await self.connector.get_vacuum_status() + vacuum_room = self.shared.current_room + last_run_stats = sensor_data.get("last_run_stats", {}) + last_loaded_map = sensor_data.get("last_loaded_map", {}) + + if not vacuum_room or last_loaded_map: + vacuum_room = {"in_room": "Unsupported"} + if not last_loaded_map: + last_loaded_map = {"name", "NoMap"} + + formatted_data = { + "mainBrush": sensor_data.get("mainBrush", 0), + "sideBrush": sensor_data.get("sideBrush", 0), + "filter": sensor_data.get("filter", 0), + "currentCleanTime": sensor_data.get("currentCleanTime", 0), + "currentCleanArea": sensor_data.get("currentCleanArea", 0), + "cleanTime": sensor_data.get("cleanTime", 0), + "cleanArea": sensor_data.get("cleanArea", 0), + "cleanCount": sensor_data.get("cleanCount", 0), + "battery": battery_level, + "state": vacuum_state, + "last_run_start": last_run_stats.get("startTime", 0), + "last_run_end": last_run_stats.get("endTime", 0), + "last_run_duration": last_run_stats.get("duration", 0), + "last_run_area": last_run_stats.get("area", 0), + "last_bin_out": sensor_data.get("last_bin_out", 0), + "last_bin_full": sensor_data.get("last_bin_full", 0), + "last_loaded_map": last_loaded_map.get("name", "NoMap"), + "robot_in_room": vacuum_room.get("in_room"), + } + return formatted_data + except Exception as err: + _LOGGER.warning(f"Error processing sensor data: {err}") + return SENSOR_NO_DATA return SENSOR_NO_DATA 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 2/5] 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: From 160131b30aa5728288feff0b9b6201dda88a86cf Mon Sep 17 00:00:00 2001 From: SCA075 <82227818+sca075@users.noreply.github.com> Date: Sat, 7 Dec 2024 11:58:13 +0100 Subject: [PATCH 3/5] updated code of download_image small refactor for coordinator sensors data. Misspell correction in async_draw_obstacle Signed-off-by: 82227818+sca075@users.noreply.github.com <82227818+sca075@users.noreply.github.com> --- .../mqtt_vacuum_camera/camera.py | 81 ++++++++----------- .../mqtt_vacuum_camera/coordinator.py | 10 +-- .../valetudo/hypfer/image_draw.py | 2 +- 3 files changed, 39 insertions(+), 54 deletions(-) diff --git a/custom_components/mqtt_vacuum_camera/camera.py b/custom_components/mqtt_vacuum_camera/camera.py index 331888ea..eb94d79c 100755 --- a/custom_components/mqtt_vacuum_camera/camera.py +++ b/custom_components/mqtt_vacuum_camera/camera.py @@ -7,7 +7,6 @@ import asyncio from asyncio import gather, get_event_loop -import concurrent.futures from concurrent.futures import ThreadPoolExecutor from datetime import timedelta from io import BytesIO @@ -58,9 +57,9 @@ async def async_setup_entry( - hass: core.HomeAssistant, - config_entry: config_entries.ConfigEntry, - async_add_entities, + hass: core.HomeAssistant, + config_entry: config_entries.ConfigEntry, + async_add_entities, ) -> None: """Setup camera from a config entry created in the integrations UI.""" config = hass.data[DOMAIN][config_entry.entry_id] @@ -146,7 +145,7 @@ def _init_clear_www_folder(self): """Remove PNG and ZIP's stored in HA config WWW""" # If enable_snapshots check if for png in www if not self._shared.enable_snapshots and os.path.isfile( - f"{self._homeassistant_path}/www/snapshot_{self._file_name}.png" + f"{self._homeassistant_path}/www/snapshot_{self._file_name}.png" ): os.remove(f"{self._homeassistant_path}/www/snapshot_{self._file_name}.png") # If there is a log zip in www remove it @@ -204,13 +203,13 @@ def is_streaming(self) -> bool: """Return true if the device is streaming.""" updated_status = self._shared.vacuum_state self._attr_is_streaming = ( - updated_status not in NOT_STREAMING_STATES - or not self._shared.vacuum_bat_charged + updated_status not in NOT_STREAMING_STATES + or not self._shared.vacuum_bat_charged ) return self._attr_is_streaming def camera_image( - self, width: Optional[int] = None, height: Optional[int] = None + self, width: Optional[int] = None, height: Optional[int] = None ) -> Optional[bytes]: """Camera Image""" return self.Image @@ -253,8 +252,8 @@ async def handle_obstacle_view(self, event): return await self.async_update() if ( - self._shared.obstacles_data - and self._shared.camera_mode == CameraModes.MAP_VIEW + self._shared.obstacles_data + and self._shared.camera_mode == CameraModes.MAP_VIEW ): _LOGGER.debug(f"Received event: {event.event_type}, Data: {event.data}") if event.data.get("entity_id") == self.entity_id: @@ -342,7 +341,7 @@ async def _async_find_nearest_obstacle(x, y, obstacles): nearest_obstacle = None min_distance = 500 # Start with a very large distance _LOGGER.debug( - f"Finding in the nearest {min_distance} piyels obstacle to coordinates: {x}, {y}" + f"Finding in the nearest {min_distance} pixels obstacle to coordinates: {x}, {y}" ) for obstacle in obstacles: @@ -375,9 +374,10 @@ async def async_open_image(self, file_path) -> Image.Image: 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): + @staticmethod + async def download_image(url: str, storage_path: str, filename: str): """ - Asynchronously download an image using threading to avoid blocking. + Asynchronously download an image without blocking. Args: url (str): The URL to download the image from. @@ -387,40 +387,25 @@ async def download_image(self, url: str, storage_path: str, filename: str): Returns: str: The full path to the saved image or None if the download fails. """ - # Ensure the storage path exists os.makedirs(storage_path, exist_ok=True) - obstacle_file = os.path.join(storage_path, filename) - async def blocking_download(): - """Run the blocking download in a separate thread.""" - try: - async with aiohttp.ClientSession() as session: - async with session.get(url) as response: - if response.status == 200: - with open(obstacle_file, "wb") as f: - f.write(await response.read()) - _LOGGER.debug( - f"Image downloaded successfully: {obstacle_file}" - ) - return obstacle_file - else: - _LOGGER.warning( - f"Failed to download image: {response.status}" - ) - return None - except Exception as e: - _LOGGER.error(f"Error downloading image: {e}") - return None + try: + async with aiohttp.ClientSession() as session: + async with session.get(url) as response: + if response.status == 200: + with open(obstacle_file, "wb") as f: + f.write(await response.read()) + _LOGGER.debug(f"Image downloaded successfully: {obstacle_file}") + return obstacle_file + else: + _LOGGER.warning(f"Failed to download image: {response.status}") + return None + except Exception as e: + _LOGGER.error(f"Error downloading image: {e}") + return None - 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( - executor, asyncio.run, blocking_download() - ) @property def should_poll(self) -> bool: @@ -513,7 +498,7 @@ async def async_update(self): f"{self._file_name}: Camera image data update available: {process_data}" ) try: - parsed_json, is_a_test = await self._process_parsed_json() + parsed_json, is_a_test = await self._process_parsed_json(True) except ValueError: self._vac_json_available = "Error" pass @@ -639,12 +624,12 @@ def _log_memory_usage(self, proc): """Log the memory usage.""" memory_percent = round( ( - (proc.memory_info()[0] / 2.0**30) - / (ProcInsp().psutil.virtual_memory().total / 2.0**30) + (proc.memory_info()[0] / 2.0**30) + / (ProcInsp().psutil.virtual_memory().total / 2.0**30) ) * 100, 2, - ) + ) _LOGGER.debug( f"{self._file_name} Camera Memory usage in GB: " f"{round(proc.memory_info()[0] / 2. ** 30, 2)}, {memory_percent}% of Total." @@ -697,8 +682,8 @@ async def run_async_pil_to_bytes(self, pil_img): pil_img_list = [pil_img for _ in range(num_processes)] loop = get_event_loop() - with concurrent.futures.ThreadPoolExecutor( - max_workers=1, thread_name_prefix=f"{self._file_name}_camera" + with (ThreadPoolExecutor( + max_workers=1, thread_name_prefix=f"{self._file_name}_camera") ) as executor: tasks = [ loop.run_in_executor( diff --git a/custom_components/mqtt_vacuum_camera/coordinator.py b/custom_components/mqtt_vacuum_camera/coordinator.py index 76d91618..7fcf6307 100644 --- a/custom_components/mqtt_vacuum_camera/coordinator.py +++ b/custom_components/mqtt_vacuum_camera/coordinator.py @@ -152,10 +152,10 @@ async def async_update_sensor_data(self, sensor_data): last_run_stats = sensor_data.get("last_run_stats", {}) last_loaded_map = sensor_data.get("last_loaded_map", {}) - if not vacuum_room or last_loaded_map: + if not vacuum_room: vacuum_room = {"in_room": "Unsupported"} - if not last_loaded_map: - last_loaded_map = {"name", "NoMap"} + if last_loaded_map=={}: + last_loaded_map = {"name", "Default"} formatted_data = { "mainBrush": sensor_data.get("mainBrush", 0), @@ -174,11 +174,11 @@ async def async_update_sensor_data(self, sensor_data): "last_run_area": last_run_stats.get("area", 0), "last_bin_out": sensor_data.get("last_bin_out", 0), "last_bin_full": sensor_data.get("last_bin_full", 0), - "last_loaded_map": last_loaded_map.get("name", "NoMap"), + "last_loaded_map": last_loaded_map.get("name", "Default"), "robot_in_room": vacuum_room.get("in_room"), } return formatted_data + return SENSOR_NO_DATA except Exception as err: _LOGGER.warning(f"Error processing sensor data: {err}") return SENSOR_NO_DATA - return SENSOR_NO_DATA 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 7a14dc27..a1778beb 100755 --- a/custom_components/mqtt_vacuum_camera/valetudo/hypfer/image_draw.py +++ b/custom_components/mqtt_vacuum_camera/valetudo/hypfer/image_draw.py @@ -128,7 +128,7 @@ async def async_draw_obstacle( ) 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 + self.img_h.shared.vacuum_api.split("http://")[1], obstacle_objects ) # Draw obstacles on the map From 606ae7f3bd2920caaf051c1dc1c842f871dd64d7 Mon Sep 17 00:00:00 2001 From: SCA075 <82227818+sca075@users.noreply.github.com> Date: Sat, 7 Dec 2024 12:44:54 +0100 Subject: [PATCH 4/5] Issue when just IPV4 is in the payload no IP return fixed. image_draw.py logging too much data for the obstacles no data logged anymore. Signed-off-by: 82227818+sca075@users.noreply.github.com <82227818+sca075@users.noreply.github.com> --- custom_components/mqtt_vacuum_camera/manifest.json | 2 +- .../mqtt_vacuum_camera/valetudo/MQTT/connector.py | 3 +++ .../mqtt_vacuum_camera/valetudo/hypfer/image_draw.py | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/custom_components/mqtt_vacuum_camera/manifest.json b/custom_components/mqtt_vacuum_camera/manifest.json index cf1f3f25..1d12f5fb 100755 --- a/custom_components/mqtt_vacuum_camera/manifest.json +++ b/custom_components/mqtt_vacuum_camera/manifest.json @@ -8,5 +8,5 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/sca075/mqtt_vacuum_camera/issues", "requirements": ["pillow>=10.3.0,<=11.0.0", "numpy"], - "version": "2024.12.0" + "version": "2024.12.1" } diff --git a/custom_components/mqtt_vacuum_camera/valetudo/MQTT/connector.py b/custom_components/mqtt_vacuum_camera/valetudo/MQTT/connector.py index f569b750..2bcd9d9d 100755 --- a/custom_components/mqtt_vacuum_camera/valetudo/MQTT/connector.py +++ b/custom_components/mqtt_vacuum_camera/valetudo/MQTT/connector.py @@ -410,6 +410,9 @@ async def async_message_received(self, msg) -> None: # When IPV4 and IPV6 are available, use IPV4 if vacuum_host_ip.split(",").__len__() > 1: self._shared.vacuum_ips = vacuum_host_ip.split(",")[0] + else: + # Use IPV4 when no IPV6 without split + self._shared.vacuum_ips = vacuum_host_ip _LOGGER.debug(f"Vacuum IPs: {self._shared.vacuum_ips}") async def async_subscribe_to_topics(self) -> None: 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 a1778beb..50cbabc1 100755 --- a/custom_components/mqtt_vacuum_camera/valetudo/hypfer/image_draw.py +++ b/custom_components/mqtt_vacuum_camera/valetudo/hypfer/image_draw.py @@ -126,14 +126,14 @@ async def async_draw_obstacle( self.img_h.shared.obstacles_data = compose_obstacle_links( self.img_h.shared.vacuum_ips, obstacle_objects ) - elif self.img_h.shared.vacuum_api: + elif self.img_h.shared.vacuum_api: # Fall back to API usage if no IP. self.img_h.shared.obstacles_data = compose_obstacle_links( self.img_h.shared.vacuum_api.split("http://")[1], obstacle_objects ) # Draw obstacles on the map if obstacle_objects: - _LOGGER.debug(f"{self.file_name} All obstacle detected: {obstacle_objects}") + _LOGGER.debug(f"{self.file_name} Obstacle detected.") self.img_h.draw.draw_obstacles(np_array, obstacle_objects, color_no_go) return np_array From b8b0fea47e883034350665a688bf04dae72b945e Mon Sep 17 00:00:00 2001 From: SCA075 <82227818+sca075@users.noreply.github.com> Date: Sat, 7 Dec 2024 12:46:41 +0100 Subject: [PATCH 5/5] ruffed files. Signed-off-by: 82227818+sca075@users.noreply.github.com <82227818+sca075@users.noreply.github.com> --- .../mqtt_vacuum_camera/camera.py | 30 +++++++++---------- .../mqtt_vacuum_camera/coordinator.py | 2 +- .../valetudo/hypfer/image_draw.py | 2 +- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/custom_components/mqtt_vacuum_camera/camera.py b/custom_components/mqtt_vacuum_camera/camera.py index eb94d79c..bf7db82f 100755 --- a/custom_components/mqtt_vacuum_camera/camera.py +++ b/custom_components/mqtt_vacuum_camera/camera.py @@ -57,9 +57,9 @@ async def async_setup_entry( - hass: core.HomeAssistant, - config_entry: config_entries.ConfigEntry, - async_add_entities, + hass: core.HomeAssistant, + config_entry: config_entries.ConfigEntry, + async_add_entities, ) -> None: """Setup camera from a config entry created in the integrations UI.""" config = hass.data[DOMAIN][config_entry.entry_id] @@ -145,7 +145,7 @@ def _init_clear_www_folder(self): """Remove PNG and ZIP's stored in HA config WWW""" # If enable_snapshots check if for png in www if not self._shared.enable_snapshots and os.path.isfile( - f"{self._homeassistant_path}/www/snapshot_{self._file_name}.png" + f"{self._homeassistant_path}/www/snapshot_{self._file_name}.png" ): os.remove(f"{self._homeassistant_path}/www/snapshot_{self._file_name}.png") # If there is a log zip in www remove it @@ -203,13 +203,13 @@ def is_streaming(self) -> bool: """Return true if the device is streaming.""" updated_status = self._shared.vacuum_state self._attr_is_streaming = ( - updated_status not in NOT_STREAMING_STATES - or not self._shared.vacuum_bat_charged + updated_status not in NOT_STREAMING_STATES + or not self._shared.vacuum_bat_charged ) return self._attr_is_streaming def camera_image( - self, width: Optional[int] = None, height: Optional[int] = None + self, width: Optional[int] = None, height: Optional[int] = None ) -> Optional[bytes]: """Camera Image""" return self.Image @@ -252,8 +252,8 @@ async def handle_obstacle_view(self, event): return await self.async_update() if ( - self._shared.obstacles_data - and self._shared.camera_mode == CameraModes.MAP_VIEW + self._shared.obstacles_data + and self._shared.camera_mode == CameraModes.MAP_VIEW ): _LOGGER.debug(f"Received event: {event.event_type}, Data: {event.data}") if event.data.get("entity_id") == self.entity_id: @@ -405,8 +405,6 @@ async def download_image(url: str, storage_path: str, filename: str): _LOGGER.error(f"Error downloading image: {e}") return None - - @property def should_poll(self) -> bool: """ON/OFF Camera Polling""" @@ -624,12 +622,12 @@ def _log_memory_usage(self, proc): """Log the memory usage.""" memory_percent = round( ( - (proc.memory_info()[0] / 2.0**30) - / (ProcInsp().psutil.virtual_memory().total / 2.0**30) + (proc.memory_info()[0] / 2.0**30) + / (ProcInsp().psutil.virtual_memory().total / 2.0**30) ) * 100, 2, - ) + ) _LOGGER.debug( f"{self._file_name} Camera Memory usage in GB: " f"{round(proc.memory_info()[0] / 2. ** 30, 2)}, {memory_percent}% of Total." @@ -682,8 +680,8 @@ async def run_async_pil_to_bytes(self, pil_img): pil_img_list = [pil_img for _ in range(num_processes)] loop = get_event_loop() - with (ThreadPoolExecutor( - max_workers=1, thread_name_prefix=f"{self._file_name}_camera") + with ThreadPoolExecutor( + max_workers=1, thread_name_prefix=f"{self._file_name}_camera" ) as executor: tasks = [ loop.run_in_executor( diff --git a/custom_components/mqtt_vacuum_camera/coordinator.py b/custom_components/mqtt_vacuum_camera/coordinator.py index 7fcf6307..9adcb00f 100644 --- a/custom_components/mqtt_vacuum_camera/coordinator.py +++ b/custom_components/mqtt_vacuum_camera/coordinator.py @@ -154,7 +154,7 @@ async def async_update_sensor_data(self, sensor_data): if not vacuum_room: vacuum_room = {"in_room": "Unsupported"} - if last_loaded_map=={}: + if last_loaded_map == {}: last_loaded_map = {"name", "Default"} formatted_data = { 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 50cbabc1..190d1e52 100755 --- a/custom_components/mqtt_vacuum_camera/valetudo/hypfer/image_draw.py +++ b/custom_components/mqtt_vacuum_camera/valetudo/hypfer/image_draw.py @@ -126,7 +126,7 @@ async def async_draw_obstacle( self.img_h.shared.obstacles_data = compose_obstacle_links( self.img_h.shared.vacuum_ips, obstacle_objects ) - elif self.img_h.shared.vacuum_api: # Fall back to API usage if no IP. + elif self.img_h.shared.vacuum_api: # Fall back to API usage if no IP. self.img_h.shared.obstacles_data = compose_obstacle_links( self.img_h.shared.vacuum_api.split("http://")[1], obstacle_objects )