Skip to content

Commit

Permalink
1.5.7.2 tests
Browse files Browse the repository at this point in the history
Signed-off-by: SCA075 <[email protected]>
  • Loading branch information
sca075 committed Feb 3, 2024
1 parent 7eeaf74 commit 5e202bc
Show file tree
Hide file tree
Showing 9 changed files with 618 additions and 526 deletions.
336 changes: 95 additions & 241 deletions custom_components/valetudo_vacuum_camera/camera.py

Large diffs are not rendered by default.

244 changes: 244 additions & 0 deletions custom_components/valetudo_vacuum_camera/camera_processing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
"""
Multiprocessing module (version 1.5.7.1)
This module provide the image multiprocessing in order to
avoid the overload of the main_thread of Home Assistant.
"""

from __future__ import annotations

import asyncio
from asyncio import gather, get_event_loop
import concurrent.futures

import logging

from .valetudo.hypfer.image_handler import (
MapImageHandler,
)
from .valetudo.valetudore.image_handler import (
ReImageHandler,
)

_LOGGER: logging.Logger = logging.getLogger(__name__)
_LOGGER.propagate = True


class CameraProcessor:
def __init__(self, camera_shared):
self._map_handler = MapImageHandler(camera_shared)
self._re_handler = ReImageHandler(camera_shared)
self._shared = camera_shared

async def async_process_valetudo_data(self, parsed_json):
"""
Compose the Camera Image from the Vacuum Json data.
:param parsed_json:
:return pil_img:
"""
if parsed_json is not None:
pil_img = await self._map_handler.async_get_image_from_json(
m_json=parsed_json,
)

if self._shared.export_svg:
self._shared.export_svg = False

if pil_img is not None:
if self._shared.map_rooms is None:
self._shared.map_rooms = (
await self._map_handler.async_get_rooms_attributes()
)
if self._shared.map_rooms:
_LOGGER.debug(
f"State attributes rooms update: {self._shared.map_rooms}"
)

if self._shared.show_vacuum_state:
status_text = (
f"{self._shared.file_name}: {self._shared.vacuum_state}"
)
text_size = 50
if self._shared.current_room:
try:
in_room = self._shared.current_room.get("in_room", None)
except (ValueError, KeyError):
text_size = 50
else:
if in_room:
text_size = 45
status_text += f", {in_room}"

self._map_handler.draw.status_text(
pil_img,
text_size,
self._shared.user_colors[8],
status_text,
)

if self._shared.attr_calibration_points is None:
self._shared.attr_calibration_points = (
self._map_handler.get_calibration_data(
self._shared.image_rotate
)
)

self._shared.vac_json_id = self._map_handler.get_json_id()

if not self._shared.charger_position:
self._shared.charger_position = (
self._map_handler.get_charger_position()
)

self._shared.current_room = self._map_handler.get_robot_position()

if not self._shared.image_size:
self._shared.image_size = self._map_handler.get_img_size()

if not self._shared.snapshot_take and (
self._shared.vacuum_state == "idle"
or self._shared.vacuum_state == "docked"
or self._shared.vacuum_state == "error"
):
# suspend image processing if we are at the next frame.
if (
self._shared.frame_number
!= self._map_handler.get_frame_number()
):
self._shared.image_grab = False
_LOGGER.info(
f"Suspended the camera data processing for: {self._shared.file_name}."
)
# take a snapshot
self._shared.snapshot_take = True
return pil_img
return None

async def async_process_rand256_data(self, parsed_json):
if parsed_json is not None:
pil_img = await self._re_handler.get_image_from_rrm(
m_json=parsed_json,
destinations=self._shared.destinations,
)

if pil_img is not None:
if self._shared.map_rooms is None:
destinations = self._shared.destinations
if destinations is not None:
(
self._shared.map_rooms,
self._shared.map_pred_zones,
self._shared.map_pred_points,
) = await self._re_handler.get_rooms_attributes(destinations)
if self._shared.map_rooms:
_LOGGER.debug(
f"State attributes rooms update: {self._shared.map_rooms}"
)
if self._shared.show_vacuum_state:
self.status_text(
pil_img,
50,
self._shared.user_colors[8],
self._shared.file_name + ": " + self._shared.vacuum_state,
)

if self._shared.attr_calibration_points is None:
self._shared.attr_calibration_points = (
self._re_handler.get_calibration_data(self._shared.image_rotate)
)

self._shared.vac_json_id = self._re_handler.get_json_id()
if not self._shared.charger_position:
self._shared.charger_position = (
self._re_handler.get_charger_position()
)
self._shared.current_room = self._re_handler.get_robot_position()
if not self._shared.image_size:
self._shared.image_size = self._re_handler.get_img_size()

if not self._shared.snapshot_take and (
self._shared.vacuum_state == "idle"
or self._shared.vacuum_state == "docked"
or self._shared.vacuum_state == "error"
):
# suspend image processing if we are at the next frame.
_LOGGER.info(
f"Suspended the camera data processing for: {self._shared.file_name}."
)
# take a snapshot
self._shared.snapshot_take = True
self._shared.image_grab = False
return pil_img
return None

def process_valetudo_data(self, parsed_json):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
if self._shared.is_rand:
result = loop.run_until_complete(
self.async_process_rand256_data(parsed_json)
)
else:
result = loop.run_until_complete(
self.async_process_valetudo_data(parsed_json)
)
finally:
loop.close()
return result

async def run_async_process_valetudo_data(self, parsed_json):
num_processes = 1
parsed_json_list = [parsed_json for _ in range(num_processes)]
loop = get_event_loop()

with concurrent.futures.ThreadPoolExecutor(
max_workers=1, thread_name_prefix=f"{self._shared.file_name}_camera"
) as executor:
tasks = [
loop.run_in_executor(executor, self.process_valetudo_data, parsed_json)
for parsed_json in parsed_json_list
]
images = await gather(*tasks)

if isinstance(images, list) and len(images) > 0:
_LOGGER.debug(f"{self._shared.file_name}: Got {len(images)} elements list..")
result = images[0]
else:
result = None

return result

def get_frame_number(self):
return self._map_handler.get_frame_number() - 1

def status_text(self, image, size, color, stat):
return self._map_handler.draw.status_text(
image=image, size=size, color=color, status=stat
)


"""
run_async_process_valetudo_data for MultiProcessing working mode.
It was tested and it works. It will be at the moment not used.
There is still no data coming back form the called function.
# async def run_async_process_valetudo_data(self, parsed_json):
# num_processes = 1
# parsed_json_list = [parsed_json for _ in range(num_processes)]
# loop = get_event_loop()
#
# with concurrent.futures.ProcessPoolExecutor() as executor:
# tasks = [
# loop.run_in_executor(executor, self.process_valetudo_data, parsed_json)
# for parsed_json in parsed_json_list
# ]
# images = await gather(*tasks)
#
# result = None
# if isinstance(images, list) and len(images) > 0:
# _LOGGER.debug(f"got {len(images)} elements list..")
# result = images[0]
#
# return result
"""
53 changes: 53 additions & 0 deletions custom_components/valetudo_vacuum_camera/camera_shared.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""
Class Camera Shared.
Keep the data between the modules.
Version 1.5.7.1
"""

import logging

from custom_components.valetudo_vacuum_camera.types import Colors

_LOGGER = logging.getLogger(__name__)


class CameraShared(object):
def __init__(self):
self.frame_number: int = 0 # camera Frame number
self.destinations: list = [] # MQTT rand destinations
self.is_rand: bool = False # MQTT rand data
self._new_mqtt_message = False # New MQTT message
self._last_image = None # Last image received
self.image_size = None # Image size
self.image_grab = True # Grab image from MQTT
self.image_rotate: int = 0 # Rotate image
self.drawing_limit: float = 0.0 # Drawing CPU limit
self.current_room = None # Current room of rhe vacuum
self.user_colors = Colors # User base colors
self.rooms_colors = Colors # Rooms colors
self.vacuum_state = None # Vacuum state
self.charger_position = None # Vacuum Charger position
self.show_vacuum_state = None # Show vacuum state on the map
self.snapshot_take = False # Take snapshot
self.vacuum_error = None # Vacuum error
self.vac_json_id = None # Vacuum json id
self.margins = None # Image margins
self.export_svg = None # Export SVG
self.svg_path = None # SVG Export path
self.file_name = None # vacuum friendly name as File name
self.attr_calibration_points = None # Calibration points of the image
self.map_rooms = None # Rooms data from the vacuum
self.map_pred_zones = None # Predefined zones data
self.map_pred_points = None # Predefined points data

def update_user_colors(self, user_colors):
self.user_colors = user_colors

def get_user_colors(self):
return self.user_colors

def update_rooms_colors(self, user_colors):
self.rooms_colors = user_colors

def get_rooms_colors(self):
return self.rooms_colors
13 changes: 7 additions & 6 deletions custom_components/valetudo_vacuum_camera/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@ async def async_step_user(self, user_input: Optional[Dict[str, Any]] = None):

for existing_entity in self._async_current_entries():
if (
existing_entity.data.get(CONF_VACUUM_ENTITY_ID) == vacuum_entity.id
or existing_entity.data.get(CONF_UNIQUE_ID) == unique_id
existing_entity.data.get(CONF_VACUUM_ENTITY_ID) == vacuum_entity.id
or existing_entity.data.get(CONF_UNIQUE_ID) == unique_id
):
return self.async_abort(reason="already_configured")

Expand Down Expand Up @@ -227,7 +227,7 @@ async def async_step_user(self, user_input: Optional[Dict[str, Any]] = None):
@staticmethod
@callback
def async_get_options_flow(
config_entry: config_entries.ConfigEntry,
config_entry: config_entries.ConfigEntry,
) -> config_entries.OptionsFlow:
"""Create the options flow."""
return OptionsFlowHandler(config_entry)
Expand Down Expand Up @@ -515,6 +515,7 @@ async def async_step_init(self, user_input=None):
async def async_step_image_opt(self, user_input: Optional[Dict[str, Any]] = None):
_LOGGER.debug("Image Options Configuration Started")
if user_input is not None:
# "get_svg_file": user_input.get(EXPORT_SVG),
self.options.update(
{
"rotate_image": user_input.get(ATTR_ROTATE),
Expand All @@ -535,7 +536,7 @@ async def async_step_image_opt(self, user_input: Optional[Dict[str, Any]] = None
)

async def async_step_base_colours(
self, user_input: Optional[Dict[str, Any]] = None
self, user_input: Optional[Dict[str, Any]] = None
):
_LOGGER.debug("Base Colours Configuration Started")
if user_input is not None:
Expand Down Expand Up @@ -589,7 +590,7 @@ async def async_step_alpha_1(self, user_input: Optional[Dict[str, Any]] = None):
)

async def async_step_rooms_colours_1(
self, user_input: Optional[Dict[str, Any]] = None
self, user_input: Optional[Dict[str, Any]] = None
):
_LOGGER.debug("Rooms Colours Configuration Started")
if user_input is not None:
Expand Down Expand Up @@ -621,7 +622,7 @@ async def async_step_rooms_colours_1(
)

async def async_step_rooms_colours_2(
self, user_input: Optional[Dict[str, Any]] = None
self, user_input: Optional[Dict[str, Any]] = None
):
_LOGGER.debug("Rooms 2/2 Colours Configuration Started")
if user_input is not None:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Snapshot Version 1.4.3"""
"""Snapshot Version 1.5.7.2"""
# Added Errors handling.

import os
Expand Down Expand Up @@ -78,7 +78,7 @@ def _zip_snapshot(self, file_name):
zf.write(log_file_name, os.path.basename(log_file_name))

# Check if the file_name.raw exists
raw_file_name = os.path.join(self.storage_path, file_name + ".raw")
raw_file_name = os.path.join(self.storage_path, f"{file_name}.raw")
if os.path.exists(raw_file_name):
# Add the .raw file to the ZIP archive
zf.write(raw_file_name, os.path.basename(raw_file_name))
Expand Down
2 changes: 1 addition & 1 deletion custom_components/valetudo_vacuum_camera/utils/img_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
ImageData is part of the Image_Handler
used functions to search data in the json
provided for the creation of the new camera frame
Last changes on Version: 1.5.6.1
Last changes on Version: 1.5.7.2
"""

import logging
Expand Down
Loading

0 comments on commit 5e202bc

Please sign in to comment.