Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
f08ebec
testing new data and async_utils.py
sca075 Aug 6, 2025
b8a126f
Corrections
sca075 Aug 6, 2025
86554d4
Corrections 2
sca075 Aug 6, 2025
c803e78
Corrections 3 async methods in auto crop optimize the image output.
sca075 Aug 7, 2025
616bbc5
update toml
sca075 Aug 7, 2025
591d325
update battery attributes.
sca075 Aug 7, 2025
8075d46
update _state_charging
sca075 Aug 7, 2025
4043c78
update _state_charging convert value to int
sca075 Aug 8, 2025
180abba
correct async_utils.py
sca075 Aug 8, 2025
4c8d6d6
correct async_utils.py
sca075 Aug 8, 2025
a8fea7e
remove old rand25_parser.py
sca075 Aug 9, 2025
4dde78c
rand256 async implemented and introducing pool array.
sca075 Aug 10, 2025
e67ad6e
removing memory expensing implemented before, changes in shared.py an…
sca075 Aug 23, 2025
6314d64
consolidating and renaming async_get_image adding bytes conversion on…
sca075 Aug 24, 2025
bb64129
removed parallel zone clean memory saving
sca075 Aug 24, 2025
4e226c9
memory treat
sca075 Aug 24, 2025
f2eb09c
updated tests, and removed panda. Improved drawable.py, reimg_draw.py…
sca075 Aug 26, 2025
c4fd824
ruff format
sca075 Aug 26, 2025
4c62476
updated init
sca075 Aug 26, 2025
925b45b
updated init agan
sca075 Aug 26, 2025
28222e7
project
sca075 Aug 26, 2025
52289d7
project final
sca075 Aug 26, 2025
1818fbf
project final
sca075 Aug 26, 2025
529b93a
Enhance robot drawing with configurable radius
sca075 Aug 27, 2025
e72e7dd
Fix indentation and formatting in drawable.py
sca075 Aug 27, 2025
7df4f41
Restrict radius to a range of 8 to 25
sca075 Aug 27, 2025
9688d89
Bump version to 0.1.10b1
sca075 Aug 27, 2025
5baf9ba
Fix angle conversion for direction wedge calculation
sca075 Aug 27, 2025
1193a47
Refactor drawing methods for improved clarity and type safety
sca075 Aug 28, 2025
df6b1d7
Refactor map_data.py with TypedDicts and type hints
sca075 Aug 28, 2025
8b4b9a3
Bump version from v0.1.6 to v0.1.10
sca075 Aug 28, 2025
3d90181
some typing imp. and implemented the HyperMapData class.
sca075 Aug 29, 2025
55818e8
fix active list creation and set, zoom now works.
sca075 Sep 6, 2025
24df28f
test shared data
sca075 Sep 6, 2025
49edffe
Merge branch 'main' into dev
sca075 Sep 6, 2025
9a13d5f
Refactor docstring formatting in draw_line function
sca075 Sep 6, 2025
bbf1d6e
ruff and bug fix
sca075 Sep 6, 2025
efea5ee
adding status_text.py and translations.py
sca075 Sep 11, 2025
a59ec13
correction on text drawing for fonts detection, init configured.
sca075 Sep 11, 2025
7b040fc
Adding Status Text#11 from sca075/status_text
sca075 Sep 11, 2025
26bfed3
Merge pull request #8 from sca075/sca075-patch-hyper_draw
sca075 Sep 11, 2025
68691ea
corrections
sca075 Sep 11, 2025
e363831
bump beta version
sca075 Sep 12, 2025
fc015c7
fix paths on init of status_text.py
sca075 Sep 12, 2025
1978c27
using external AutoCrop and removing debug logs.
sca075 Sep 20, 2025
38ff67d
ruff format correction
sca075 Sep 20, 2025
ae5e35b
remove mcv rennder test
sca075 Sep 21, 2025
7d03d07
add mcvrender and bump version
sca075 Sep 21, 2025
e152125
test mcvrender == 0.0.2 and bump version
sca075 Sep 21, 2025
0578531
test mcvrender == 0.0.2 and bump version
sca075 Sep 21, 2025
7ac2780
update workflows
sca075 Sep 21, 2025
90abb22
corrections on aspect ratio calculation
sca075 Sep 22, 2025
2ee063b
Bump version to 0.1.10b9.1
sca075 Sep 22, 2025
2ad9be6
Bump version to 0.1.10rc1
sca075 Sep 22, 2025
b8fab05
Quick tested the calibration data okay and change utils.py
sca075 Sep 23, 2025
7d95b52
Merge remote-tracking branch 'origin/dev' into dev_main
Sep 23, 2025
118d377
update version
sca075 Sep 23, 2025
1e85a9b
Merge branch 'main' into dev
sca075 Sep 23, 2025
9aae79d
optimized BaseHandler and removed redundant functions from the Handle…
sca075 Sep 26, 2025
9b07df3
errors in resize "," or ":" parameters from config handling.
sca075 Sep 26, 2025
e437655
dam shared size
sca075 Sep 26, 2025
1b5622b
Enhance type hints and refactor data classes
sca075 Sep 27, 2025
8c4e832
Refactor map_data.py #9 from sca075/sca075-Image_data_refactor
sca075 Sep 27, 2025
9c85cdc
Update SCR/valetudo_map_parser/map_data.py
sca075 Sep 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/code_quality.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
uses: actions/setup-python@v6
id: python
with:
python-version: "3.12"
python-version: "3.13"

- name: Install Poetry
run: pip install poetry
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ jobs:
- uses: actions/checkout@v5

# Step 2: Set up Python
- name: Set up Python 3.12
- name: Set up Python 3.13
uses: actions/setup-python@v6
with:
python-version: "3.12"
python-version: "3.13"

# Step 3: Install Poetry
- name: Install Poetry
Expand Down
2 changes: 1 addition & 1 deletion SCR/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
"""Valetudo map parser.
Version: 0.1.9"""
Version: 0.1.10"""
20 changes: 19 additions & 1 deletion SCR/valetudo_map_parser/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Valetudo map parser.
Version: 0.1.9"""
Version: 0.1.10"""

from pathlib import Path

from .config.colors import ColorsManagement
from .config.drawable import Drawable
Expand All @@ -19,14 +21,27 @@
NumpyArray,
ImageSize,
)
from .config.status_text.status_text import StatusText
from .config.status_text.translations import translations as STATUS_TEXT_TRANSLATIONS
from .hypfer_handler import HypferMapImageHandler
from .rand256_handler import ReImageHandler
from .rooms_handler import RoomsHandler, RandRoomsHandler
from .map_data import HyperMapData


def get_default_font_path() -> str:
"""Return the absolute path to the bundled default font directory.

This returns the path to the fonts folder; the caller can join a specific font file
to avoid hard-coding a particular font here.
"""
return str((Path(__file__).resolve().parent / "config" / "fonts").resolve())


__all__ = [
"RoomsHandler",
"RandRoomsHandler",
"HyperMapData",
"HypferMapImageHandler",
"ReImageHandler",
"RRMapParser",
Expand All @@ -47,4 +62,7 @@
"PilPNG",
"NumpyArray",
"ImageSize",
"StatusText",
"STATUS_TEXT_TRANSLATIONS",
"get_default_font_path",
]
29 changes: 2 additions & 27 deletions SCR/valetudo_map_parser/config/auto_crop.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@
import logging

import numpy as np
from numpy import rot90
from scipy import ndimage

from .async_utils import AsyncNumPy, make_async
from .async_utils import AsyncNumPy
from .types import Color, NumpyArray, TrimCropData, TrimsData
from .utils import BaseHandler

Expand Down Expand Up @@ -91,7 +90,6 @@ def _calculate_trimmed_dimensions(self):

async def _async_auto_crop_data(self, tdata: TrimsData): # , tdata=None
"""Load the auto crop data from the Camera config."""
_LOGGER.debug("Auto Crop init data: %s, %s", str(tdata), str(self.auto_crop))
if not self.auto_crop:
trims_data = TrimCropData.from_dict(dict(tdata.to_dict())).to_list()
(
Expand All @@ -100,7 +98,6 @@ async def _async_auto_crop_data(self, tdata: TrimsData): # , tdata=None
self.trim_right,
self.trim_down,
) = trims_data
_LOGGER.debug("Auto Crop trims data: %s", trims_data)
if trims_data != [0, 0, 0, 0]:
self._calculate_trimmed_dimensions()
else:
Expand All @@ -118,10 +115,6 @@ def auto_crop_offset(self):

async def _init_auto_crop(self):
"""Initialize the auto crop data."""
_LOGGER.debug("Auto Crop Init data: %s", str(self.auto_crop))
_LOGGER.debug(
"Auto Crop Init trims data: %r", self.handler.shared.trims.to_dict()
)
if not self.auto_crop: # and self.handler.shared.vacuum_state == "docked":
self.auto_crop = await self._async_auto_crop_data(self.handler.shared.trims)
if self.auto_crop:
Expand All @@ -131,7 +124,6 @@ async def _init_auto_crop(self):

# Fallback: Ensure auto_crop is valid
if not self.auto_crop or any(v < 0 for v in self.auto_crop):
_LOGGER.debug("Auto-crop data unavailable. Scanning full image.")
self.auto_crop = None

return self.auto_crop
Expand Down Expand Up @@ -164,14 +156,6 @@ async def async_image_margins(
min_y, max_y = y_slice.start, y_slice.stop - 1
min_x, max_x = x_slice.start, x_slice.stop - 1

_LOGGER.debug(
"%s: Found trims max and min values (y,x) (%s, %s) (%s, %s)...",
self.handler.file_name,
int(max_y),
int(max_x),
int(min_y),
int(min_x),
)
return min_y, min_x, max_x, max_y

async def async_get_room_bounding_box(
Expand Down Expand Up @@ -247,7 +231,7 @@ async def async_get_room_bounding_box(
return None

except Exception as e:
_LOGGER.error(
_LOGGER.warning(
"%s: Error calculating room bounding box for '%s': %s",
self.handler.file_name,
room_name,
Expand Down Expand Up @@ -403,7 +387,6 @@ async def async_auto_trim_and_zoom_image(
try:
self.auto_crop = await self._init_auto_crop()
if (self.auto_crop is None) or (self.auto_crop == [0, 0, 0, 0]):
_LOGGER.debug("%s: Calculating auto trim box", self.handler.file_name)
# Find the coordinates of the first occurrence of a non-background color
min_y, min_x, max_x, max_y = await self.async_image_margins(
image_array, detect_colour
Expand Down Expand Up @@ -456,15 +439,7 @@ async def async_auto_trim_and_zoom_image(
# Rotate the cropped image based on the given angle
rotated = await self.async_rotate_the_image(trimmed, rotate)
del trimmed # Free memory.
_LOGGER.debug(
"%s: Auto Trim Box data: %s", self.handler.file_name, self.crop_area
)
self.handler.crop_img_size = [rotated.shape[1], rotated.shape[0]]
_LOGGER.debug(
"%s: Auto Trimmed image size: %s",
self.handler.file_name,
self.handler.crop_img_size,
)

except RuntimeError as e:
_LOGGER.warning(
Expand Down
4 changes: 2 additions & 2 deletions SCR/valetudo_map_parser/config/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ def add_alpha_to_rgb(alpha_channels, rgb_colors):
List[Tuple[int, int, int, int]]: List of RGBA colors with alpha channel added.
"""
if len(alpha_channels) != len(rgb_colors):
LOGGER.error("Input lists must have the same length.")
LOGGER.warning("Input lists must have the same length.")
return []

# Fast path for empty lists
Expand Down Expand Up @@ -357,7 +357,7 @@ def set_initial_colours(self, device_info: dict) -> None:
self.color_cache.clear()

except (ValueError, IndexError, UnboundLocalError) as e:
LOGGER.error("Error while populating colors: %s", e)
LOGGER.warning("Error while populating colors: %s", e)

def initialize_user_colors(self, device_info: dict) -> List[Color]:
"""
Expand Down
25 changes: 20 additions & 5 deletions SCR/valetudo_map_parser/config/drawable.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from __future__ import annotations

import logging
from pathlib import Path

import numpy as np
from PIL import Image, ImageDraw, ImageFont
Expand Down Expand Up @@ -874,11 +875,25 @@ def status_text(
position: bool,
) -> None:
"""Draw the status text on the image."""
path_default_font = (
"custom_components/mqtt_vacuum_camera/utils/fonts/FiraSans.ttf"
)
default_font = ImageFont.truetype(path_default_font, size)
user_font = ImageFont.truetype(path_font, size)
module_dir = Path(__file__).resolve().parent
default_font_path = module_dir / "fonts" / "FiraSans.ttf"
# Load default font with safety fallback to PIL's built-in if missing
try:
default_font = ImageFont.truetype(str(default_font_path), size)
except OSError:
_LOGGER.warning(
"Default font not found at %s; using PIL default font",
default_font_path,
)
default_font = ImageFont.load_default()

# Use provided font directly if available; else fall back to default
user_font = default_font
if path_font:
try:
user_font = ImageFont.truetype(str(path_font), size)
except OSError:
user_font = default_font
if position:
x, y = 10, 10
else:
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
33 changes: 22 additions & 11 deletions SCR/valetudo_map_parser/config/shared.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
Class Camera Shared.
Keep the data between the modules.
Version: v0.1.9
Version: v0.1.10
"""

import asyncio
Expand Down Expand Up @@ -58,6 +58,7 @@ def __init__(self, file_name):
self.frame_number: int = 0 # camera Frame number
self.destinations: list = [] # MQTT rand destinations
self.rand256_active_zone: list = [] # Active zone for rand256
self.rand256_zone_coordinates: list = [] # Active zone coordinates for rand256
self.is_rand: bool = False # MQTT rand data
self._new_mqtt_message = False # New MQTT message
# Initialize last_image with default gray image (250x150 minimum)
Expand All @@ -69,6 +70,7 @@ def __init__(self, file_name):
self.image_last_updated: float = 0.0 # Last image update time
self.image_format = "image/pil" # Image format
self.image_size = None # Image size
self.robot_size = None # Robot size
self.image_auto_zoom: bool = False # Auto zoom image
self.image_zoom_lock_ratio: bool = True # Zoom lock ratio
self.image_ref_height: int = 0 # Image reference height
Expand All @@ -81,8 +83,7 @@ def __init__(self, file_name):
self.user_colors = Colors # User base colors
self.rooms_colors = Colors # Rooms colors
self.vacuum_battery = 0 # Vacuum battery state
self.vacuum_bat_charged: bool = True # Vacuum charged and ready
self.vacuum_connection = None # Vacuum connection state
self.vacuum_connection = False # Vacuum connection state
self.vacuum_state = None # Vacuum state
self.charger_position = None # Vacuum Charger position
self.show_vacuum_state = None # Show vacuum state on the map
Expand Down Expand Up @@ -197,14 +198,13 @@ def generate_attributes(self) -> dict:
attrs = {
ATTR_CAMERA_MODE: self.camera_mode,
ATTR_VACUUM_BATTERY: f"{self.vacuum_battery}%",
ATTR_VACUUM_CHARGING: self.vacuum_bat_charged,
ATTR_VACUUM_CHARGING: self.vacuum_bat_charged(),
ATTR_VACUUM_POSITION: self.current_room,
ATTR_VACUUM_STATUS: self.vacuum_state,
ATTR_VACUUM_JSON_ID: self.vac_json_id,
ATTR_CALIBRATION_POINTS: self.attr_calibration_points,
}
if self.obstacles_pos and self.vacuum_ips:
_LOGGER.debug("Generating obstacle links from: %s", self.obstacles_pos)
self.obstacles_data = self._compose_obstacle_links(
self.vacuum_ips, self.obstacles_pos
)
Expand Down Expand Up @@ -302,19 +302,30 @@ def update_shared_data(self, device_info):
)
# Ensure trims are updated correctly
trim_data = device_info.get("trims_data", DEFAULT_VALUES["trims_data"])
_LOGGER.debug(
"%s: Updating shared trims with: %s", instance.file_name, trim_data
)
instance.trims = TrimsData.from_dict(trim_data)
# Robot size
robot_size = device_info.get("robot_size", 25)
try:
robot_size = int(robot_size)
except (ValueError, TypeError):
robot_size = 25
# Clamp robot_size to [8, 25]
if robot_size < 8:
robot_size = 8
elif robot_size > 25:
robot_size = 25
instance.robot_size = robot_size

except TypeError as ex:
_LOGGER.error("Shared data can't be initialized due to a TypeError! %s", ex)
_LOGGER.warning(
"Shared data can't be initialized due to a TypeError! %s", ex
)
except AttributeError as ex:
_LOGGER.error(
_LOGGER.warning(
"Shared data can't be initialized due to an AttributeError! %s", ex
)
except RuntimeError as ex:
_LOGGER.error(
_LOGGER.warning(
"An unexpected error occurred while initializing shared data %s:", ex
)

Expand Down
Loading