Skip to content

Comments

Update dependencies and optimize memory management#62

Merged
sca075 merged 42 commits intomainfrom
dev
Feb 21, 2026
Merged

Update dependencies and optimize memory management#62
sca075 merged 42 commits intomainfrom
dev

Conversation

@sca075
Copy link
Owner

@sca075 sca075 commented Feb 12, 2026

chore:

  • Update dependencies to match Home Assistant 2025.x requirements:

    • numpy: >=1.26.4 → >=2.0.0 (HA uses 2.3.2)
    • Pillow: >=10.3.0 → >=12.0.0 (HA uses 12.0.0)
    • scipy: >=1.12.0 → >=1.17.0 (latest stable)
    • mvcrender: kept at ==0.0.9 (breaking changes in 0.1.0)
  • Fix memory optimization in hypfer_handler:

    • Clear img_work_layer after auto-crop to free persistent buffer
    • Reduces memory growth to 0.12 MB/frame (3x better than target)
  • Fix dock_state display logic:

    • Remove vacuum_state dependency from dock_state display
    • Dock maintenance operations now show independently of robot state
  • Improve test configuration:

    • Add ENABLE_PIL_SHOW flag to disable GUI overhead
    • Add SAVE_FIRST_FRAME flag for controlled file I/O
    • Re-enable material rendering in tests
  • Code quality improvements:

    • Fix isort formatting in status_text/init.py
    • All production code passes QA: Ruff ✓, isort ✓, Pylint 10/10

Performance: 100 frames @ 106ms avg, Memory: 0.12 MB/frame growth

Quality Gate:

  • Ruff: All checks passed!
  • isort: All files correctly formatted
  • Pylint: Score: 10.00/10
  • Tests: Performance validated on 100-frame test run

Summary by CodeRabbit

  • Version Release

    • Released version 0.2.5b1
  • New Features

    • Added image conversion utilities and obstacle-link configuration options exposed in the public API
    • Broader dock status reporting to surface dock-related info in more device states
  • Performance

    • Reduced memory usage during image processing and quieter image error logging
  • Tests

    • More aggressive/default profiling and memory-capture; added calibration overlays and bytes-format consistency tests
  • Chores

    • Added IDE project configuration and ignore entries

sca075 added 30 commits February 7, 2026 20:41
…e code quality to 10/10

- Add dock_state attribute to track docking station operations (self_cleaning, emptying, etc.)
- Refactor map_data.py to reduce complexity (extract helper methods)
- Refactor drawable_elements.py to use dictionary-driven loops
- Fix exception handling in hypfer_draw.py (use specific exceptions)
- Remove commented-out code from hypfer_draw.py

Quality improvements:
- Pylint score: 10.00/10 (perfect score)
- Fixed W0718: Replace broad Exception with specific exceptions
- Fixed R0912: Reduce branches from 16→6 (map_data) and 14→10 (drawable_elements)
- Fixed R1702: Reduce nested blocks from 7→3 (map_data)
- All code formatted with ruff and isort

Modified files:
- SCR/valetudo_map_parser/const.py
- SCR/valetudo_map_parser/__init__.py
- SCR/valetudo_map_parser/config/shared.py
- SCR/valetudo_map_parser/config/status_text/status_text.py
- SCR/valetudo_map_parser/map_data.py
- SCR/valetudo_map_parser/hypfer_draw.py
- SCR/valetudo_map_parser/config/drawable_elements.py

Tests: ✅ All passing (440.8ms image generation)

fix: Convert float device_info values to integers (Pi4 compatibility)
fix: Correct calibration points coordinate swap

## Pi4 Float to Integer Conversion

On Raspberry Pi 4 and other systems, Home Assistant can pass device_info
configuration values as floats (e.g., 255.0, 100.0) even when they should
be integers. This caused "TypeError: 'float' object cannot be interpreted
as an integer" when AutoCrop initialization accessed these values.

Changes:
- Convert offset values (offset_top, offset_down, offset_left, offset_right) to int
- Convert size values (vacuum_status_size, mop_path_width) to int with error handling
- Update TrimsData.from_dict(), from_json(), and from_list() to convert trim values to int
- Update TrimCropData.from_dict() and from_list() to convert trim values to int

Note: Color alpha values intentionally remain as floats in device_info and are
converted to integers only when creating final RGBA tuples, so color handling
is unaffected.

## Calibration Points Coordinate Swap Fix

Fixed critical bug where X and Y coordinates were swapped in calibration points
calculation. The crop_area format is [left, up, right, down], but get_vacuum_points()
was incorrectly mapping crop_area[0] (left) to x and crop_area[1] (up) to y.

Corrected mapping:
- crop_area[1] (up) → x (was crop_area[0])
- crop_area[0] (left) → y (was crop_area[1])

Impact:
- Robot position on map is now correctly placed
- Touch-to-go coordinates work correctly
- Room detection reports the correct room
- Fixes issue where robot at x:3464, y:3262 was showing [3162, 3442] instead of [3442, 3162]

## Test Fixes

- Fix typo in tests/test.py: trim_left 240.0 → 2400.0

## Version

- Bump version from 0.2.3 to 0.2.4b1 for beta testing in Home Assistant

Files modified:
- pyproject.toml
- SCR/valetudo_map_parser/__init__.py
- SCR/valetudo_map_parser/config/shared.py
- SCR/valetudo_map_parser/config/types.py
- SCR/valetudo_map_parser/config/utils.py
- tests/test.py

Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
- Fix active_list alignment in map_data.py: append 0 on conversion failure instead of skipping
- Remove duplicate color_mop_move and alpha_mop_move keys in tests/test.py

Ensures segment-to-active-zone index correspondence and eliminates dict key override issues.

Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
… version 0.2.4b3 release

🐛 Critical Bug Fixes:
- Fixed calibration points not updating when map rotation changes
  * Removed incorrect rotation reordering logic in get_vacuum_points()
  * Calibration data now recalculates on every image generation
  * Ensures coordinate mapping works correctly at all rotation angles
- Fixed segment alignment bug in map_data.py
- Fixed rotation change handling to prevent "Invalid crop region" errors
  * Added rotation tracking to FloorData class
  * Detects when saved rotation differs from current rotation
  * Automatically resets trims for recalculation when rotation changes
  * Prevents errors when user changes rotation with saved floor data

✨ Implementation Details:
- Added rotation: int field to FloorData class
- update_trims() now saves current image_rotate with trims
- On reload, compares saved rotation with current rotation
- If different, resets trims to defaults and lets auto-crop recalculate
- Logs rotation changes for debugging

✨ Updates:
- Version bumped to 0.2.4b3
- Updated mvcrender dependency to 0.1.0
  https://github.com/sca075/mvcrender/releases/tag/v0.1.0
- Comprehensive documentation updates (README.md, CHANGELOG.md)
- New test: test_reload_rotation_change.py

✅ Quality Gate:
- Pylint: 10.00/10 - All checks passed
- Tests: PASSED
  * test_autocrop_floor.py: Calibration points correctly change with rotation
  * test_reload_rotation_change.py: Rotation change detection working, trims reset and recalculated
  * test.py: Image generation 432.6ms avg

📝 Modified Files:
- SCR/valetudo_map_parser/config/types.py (rotation field in FloorData)
- SCR/valetudo_map_parser/config/utils.py (update_trims with rotation, calibration fixes)
- SCR/valetudo_map_parser/config/shared.py (rotation change detection)
- tests/test_reload_rotation_change.py (new test)
- pyproject.toml (version bump, mvcrender 0.1.0)
- README.md (version and features update)
- CHANGELOG.md (comprehensive v0.2.4 release notes with rotation change handling)

Ready for Home Assistant testing.

Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
…rop_area mapping

CRITICAL FIX: Fixed TrimsData.from_list() mapping where crop_area indices
were completely backwards, causing impossible trim values (trim_left > trim_right)
and broken calibration points.

The crop_area format is [left, up, right, down], so the correct mapping is:
- crop_area[0] → trim_left (was incorrectly mapped to trim_up)
- crop_area[1] → trim_up (was incorrectly mapped to trim_left)
- crop_area[2] → trim_right (was incorrectly mapped to trim_down)
- crop_area[3] → trim_down (was incorrectly mapped to trim_right)

This bug caused:
- Calibration points to be completely wrong
- Click-to-go to send vacuum to wrong locations
- Room boundaries to be incorrect
- All coordinate mapping features to fail

Test verification (test_autocrop_floor.py):
✅ All trim values now satisfy: trim_left < trim_right and trim_up < trim_down
✅ Calibration points correctly track vacuum position across rotations
✅ Robot position consistent: {'x': 3231, 'y': 3275, 'angle': 90.0}

Modified:
- SCR/valetudo_map_parser/config/types.py (lines 333-345)

Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
…rop_area mapping

CRITICAL FIX: Fixed TrimsData.from_list() mapping where crop_area indices
were completely backwards, causing impossible trim values (trim_left > trim_right)
and broken calibration points.

The crop_area format is [left, up, right, down], so the correct mapping is:
- crop_area[0] → trim_left (was incorrectly mapped to trim_up)
- crop_area[1] → trim_up (was incorrectly mapped to trim_left)
- crop_area[2] → trim_right (was incorrectly mapped to trim_down)
- crop_area[3] → trim_down (was incorrectly mapped to trim_right)

This bug caused:
- Calibration points to be completely wrong
- Click-to-go to send vacuum to wrong locations
- Room boundaries to be incorrect
- All coordinate mapping features to fail

Test verification (test_autocrop_floor.py):
✅ All trim values now satisfy: trim_left < trim_right and trim_up < trim_down
✅ Calibration points correctly track vacuum position across rotations
✅ Robot position consistent: {'x': 3231, 'y': 3275, 'angle': 90.0}

Modified:
- SCR/valetudo_map_parser/config/types.py (lines 333-345)

Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
CRITICAL FIX: Fixed calibration coordinate system mismatch between NumPy (y,x)
and Home Assistant (x,y). NumPy uses (row, column) indexing while Home Assistant
expects standard Cartesian (x, y) coordinates.

Changes:
- Swapped X and Y coordinates in get_vacuum_points() to compensate for the
  coordinate system difference
- Fixed TrimsData.from_list() to correctly map crop_area indices
- Added detailed logging for calibration calculation debugging

The Problem:
- mvcrender outputs crop_area in NumPy (y, x) format
- Home Assistant expects (x, y) Cartesian coordinates
- Without the swap, calibration points had X and Y reversed
- This caused click-to-go to send vacuum to wrong locations

The Solution:
In get_vacuum_points(), we now swap coordinates:
  x = crop_area[1] + offset_y  (use 'up' for x)
  y = crop_area[0] + offset_x  (use 'left' for y)

Impact:
- Calibration points now correctly map image pixels to vacuum coordinates
- Click-to-go functionality works correctly
- All rotation angles (0°, 90°, 180°, 270°) produce correct calibration

Test Results:
✅ test_autocrop_floor.py passes with correct calibration for all rotations
✅ Coordinates now match Home Assistant's expected format

Modified:
- SCR/valetudo_map_parser/config/utils.py (get_vacuum_points)
- SCR/valetudo_map_parser/config/types.py (TrimsData.from_list)
- SCR/valetudo_map_parser/hypfer_handler.py (added logging)

Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
…e code quality to 10/10

- Add dock_state attribute to track docking station operations (self_cleaning, emptying, etc.)
- Refactor map_data.py to reduce complexity (extract helper methods)
- Refactor drawable_elements.py to use dictionary-driven loops
- Fix exception handling in hypfer_draw.py (use specific exceptions)
- Remove commented-out code from hypfer_draw.py

Quality improvements:
- Pylint score: 10.00/10 (perfect score)
- Fixed W0718: Replace broad Exception with specific exceptions
- Fixed R0912: Reduce branches from 16→6 (map_data) and 14→10 (drawable_elements)
- Fixed R1702: Reduce nested blocks from 7→3 (map_data)
- All code formatted with ruff and isort

Modified files:
- SCR/valetudo_map_parser/const.py
- SCR/valetudo_map_parser/__init__.py
- SCR/valetudo_map_parser/config/shared.py
- SCR/valetudo_map_parser/config/status_text/status_text.py
- SCR/valetudo_map_parser/map_data.py
- SCR/valetudo_map_parser/hypfer_draw.py
- SCR/valetudo_map_parser/config/drawable_elements.py

Tests: ✅ All passing (440.8ms image generation)

fix: Convert float device_info values to integers (Pi4 compatibility)
fix: Correct calibration points coordinate swap

## Pi4 Float to Integer Conversion

On Raspberry Pi 4 and other systems, Home Assistant can pass device_info
configuration values as floats (e.g., 255.0, 100.0) even when they should
be integers. This caused "TypeError: 'float' object cannot be interpreted
as an integer" when AutoCrop initialization accessed these values.

Changes:
- Convert offset values (offset_top, offset_down, offset_left, offset_right) to int
- Convert size values (vacuum_status_size, mop_path_width) to int with error handling
- Update TrimsData.from_dict(), from_json(), and from_list() to convert trim values to int
- Update TrimCropData.from_dict() and from_list() to convert trim values to int

Note: Color alpha values intentionally remain as floats in device_info and are
converted to integers only when creating final RGBA tuples, so color handling
is unaffected.

## Calibration Points Coordinate Swap Fix

Fixed critical bug where X and Y coordinates were swapped in calibration points
calculation. The crop_area format is [left, up, right, down], but get_vacuum_points()
was incorrectly mapping crop_area[0] (left) to x and crop_area[1] (up) to y.

Corrected mapping:
- crop_area[1] (up) → x (was crop_area[0])
- crop_area[0] (left) → y (was crop_area[1])

Impact:
- Robot position on map is now correctly placed
- Touch-to-go coordinates work correctly
- Room detection reports the correct room
- Fixes issue where robot at x:3464, y:3262 was showing [3162, 3442] instead of [3442, 3162]

## Test Fixes

- Fix typo in tests/test.py: trim_left 240.0 → 2400.0

## Version

- Bump version from 0.2.3 to 0.2.4b1 for beta testing in Home Assistant

Files modified:
- pyproject.toml
- SCR/valetudo_map_parser/__init__.py
- SCR/valetudo_map_parser/config/shared.py
- SCR/valetudo_map_parser/config/types.py
- SCR/valetudo_map_parser/config/utils.py
- tests/test.py

Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
… version 0.2.4b3 release

🐛 Critical Bug Fixes:
- Fixed calibration points not updating when map rotation changes
  * Removed incorrect rotation reordering logic in get_vacuum_points()
  * Calibration data now recalculates on every image generation
  * Ensures coordinate mapping works correctly at all rotation angles
- Fixed segment alignment bug in map_data.py
- Fixed rotation change handling to prevent "Invalid crop region" errors
  * Added rotation tracking to FloorData class
  * Detects when saved rotation differs from current rotation
  * Automatically resets trims for recalculation when rotation changes
  * Prevents errors when user changes rotation with saved floor data

✨ Implementation Details:
- Added rotation: int field to FloorData class
- update_trims() now saves current image_rotate with trims
- On reload, compares saved rotation with current rotation
- If different, resets trims to defaults and lets auto-crop recalculate
- Logs rotation changes for debugging

✨ Updates:
- Version bumped to 0.2.4b3
- Updated mvcrender dependency to 0.1.0
  https://github.com/sca075/mvcrender/releases/tag/v0.1.0
- Comprehensive documentation updates (README.md, CHANGELOG.md)
- New test: test_reload_rotation_change.py

✅ Quality Gate:
- Pylint: 10.00/10 - All checks passed
- Tests: PASSED
  * test_autocrop_floor.py: Calibration points correctly change with rotation
  * test_reload_rotation_change.py: Rotation change detection working, trims reset and recalculated
  * test.py: Image generation 432.6ms avg

📝 Modified Files:
- SCR/valetudo_map_parser/config/types.py (rotation field in FloorData)
- SCR/valetudo_map_parser/config/utils.py (update_trims with rotation, calibration fixes)
- SCR/valetudo_map_parser/config/shared.py (rotation change detection)
- tests/test_reload_rotation_change.py (new test)
- pyproject.toml (version bump, mvcrender 0.1.0)
- README.md (version and features update)
- CHANGELOG.md (comprehensive v0.2.4 release notes with rotation change handling)

Ready for Home Assistant testing.

Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
…the calibration steps we downgraded it to 0.8.0 to fit the current implementation.

Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
…the calibration steps we downgraded it to 0.8.0 to fit the current implementation.

Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
…ices

Version 0.2.4 - Production Ready Release

Major Changes:
- Python 3.12+ support (Home Assistant compatible)
- Converted all logging to DEBUG level (32 instances across 9 files)
- Modernized Rand256 calibration with match/case pattern
- Code cleanup: removed 40 lines of unnecessary comments

Library Logging Best Practices:
- All LOGGER.info → LOGGER.debug (3 instances)
- All LOGGER.warning → LOGGER.debug (29 instances)
- Applications now control library log output
- Production logs stay clean unless DEBUG enabled

Code Quality:
- Pylint: 9.99/10
- Ruff: All checks passed
- isort: All imports sorted
- Tests: All passing - calibration working at all rotations

Modified Files:
- SCR/valetudo_map_parser/config/shared.py (6 logging conversions)
- SCR/valetudo_map_parser/config/utils.py (6 logging conversions + comment cleanup)
- SCR/valetudo_map_parser/rand256_handler.py (3 logging conversions)
- SCR/valetudo_map_parser/reimg_draw.py (4 logging conversions)
- SCR/valetudo_map_parser/hypfer_draw.py (7 logging conversions)
- SCR/valetudo_map_parser/hypfer_handler.py (2 logging conversions)
- SCR/valetudo_map_parser/config/colors.py (2 logging conversions)
- SCR/valetudo_map_parser/config/drawable.py (1 logging conversion)
- SCR/valetudo_map_parser/rooms_handler.py (1 logging conversion)

Breaking Changes: None
Dependencies: mvcrender==0.0.9 (Python 3.12 compatible)

Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
…ices

Version 0.2.4 - Production Ready Release

Major Changes:
- Python 3.12+ support (Home Assistant compatible)
- Converted all logging to DEBUG level (32 instances across 9 files)
- Modernized Rand256 calibration with match/case pattern
- Code cleanup: removed 40 lines of unnecessary comments

Library Logging Best Practices:
- All LOGGER.info → LOGGER.debug (3 instances)
- All LOGGER.warning → LOGGER.debug (29 instances)
- Applications now control library log output
- Production logs stay clean unless DEBUG enabled

Code Quality:
- Pylint: 9.99/10
- Ruff: All checks passed
- isort: All imports sorted
- Tests: All passing - calibration working at all rotations

Modified Files:
- SCR/valetudo_map_parser/config/shared.py (6 logging conversions)
- SCR/valetudo_map_parser/config/utils.py (6 logging conversions + comment cleanup)
- SCR/valetudo_map_parser/rand256_handler.py (3 logging conversions)
- SCR/valetudo_map_parser/reimg_draw.py (4 logging conversions)
- SCR/valetudo_map_parser/hypfer_draw.py (7 logging conversions)
- SCR/valetudo_map_parser/hypfer_handler.py (2 logging conversions)
- SCR/valetudo_map_parser/config/colors.py (2 logging conversions)
- SCR/valetudo_map_parser/config/drawable.py (1 logging conversion)
- SCR/valetudo_map_parser/rooms_handler.py (1 logging conversion)

Breaking Changes: None
Dependencies: mvcrender==0.0.9 (Python 3.12 compatible)

Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
Added pylint disable comments for dynamic method calls that exist in subclasses.

Changes:
- Line 215: get_rooms_attributes() - exists in Rand256Handler subclass
- Line 223: async_get_rooms_attributes() - exists in HypferHandler subclass

Both methods are safely checked with hasattr() before use, but pylint cannot
detect dynamic method resolution in base classes. The disable comments are
properly documented to explain why the methods exist at runtime.

Quality Gate:
- Pylint: 10.00/10 (improved from 9.77/10)
- Ruff: All checks passed
- isort: All imports sorted
- Tests: All passing

Breaking Changes: None

Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
Added pylint disable comments for dynamic method calls that exist in subclasses.

Changes:
- Line 215: get_rooms_attributes() - exists in Rand256Handler subclass
- Line 223: async_get_rooms_attributes() - exists in HypferHandler subclass

Both methods are safely checked with hasattr() before use, but pylint cannot
detect dynamic method resolution in base classes. The disable comments are
properly documented to explain why the methods exist at runtime.

Quality Gate:
- Pylint: 10.00/10 (improved from 9.77/10)
- Ruff: All checks passed
- isort: All imports sorted
- Tests: All passing

Breaking Changes: None

Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
- Update numpy (>=2.0.0), Pillow (>=12.0.0), scipy (>=1.17.0) for HA compatibility
- Optimize memory: clear persistent buffer after auto-crop (0.12 MB/frame growth)
- Fix dock_state display to work independently of vacuum_state
- Improve test configuration with ENABLE_PIL_SHOW and SAVE_FIRST_FRAME flags
- Code quality: All checks pass (Ruff ✓, isort ✓, Pylint 10/10)

Performance: 106ms avg per frame, 0.12 MB/frame memory growth (3x better than target)
Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
@sca075 sca075 self-assigned this Feb 12, 2026
@sca075 sca075 added the enhancement New feature or request label Feb 12, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds explicit exports, new obstacle-link configuration and public utilities, broadens dock_state inclusion in status text, refines image-processing memory handling and logging, bumps package and dependency versions, augments tests (image/overlay/profiling), and adds IDE project files.

Changes

Cohort / File(s) Summary
Status text
SCR/valetudo_map_parser/config/status_text/__init__.py, SCR/valetudo_map_parser/config/status_text/status_text.py
Export list __all__ = ["StatusText", "translations"] added; _dock_state logic changed to include dock_state whenever set and not "idle", independent of vacuum_state.
Obstacle link config & public API
SCR/valetudo_map_parser/__init__.py, SCR/valetudo_map_parser/const.py, SCR/valetudo_map_parser/config/shared.py
Package version bumped to 0.2.5b1; added CONF_OBSTACLE_LINK_IP, CONF_OBSTACLE_LINK_PORT, CONF_OBSTACLE_LINK_PROTOCOL; exposed them and new image utilities (pil_to_jpeg_bytes, pil_to_png_bytes) in top-level __all__; CameraShared gains obstacle_link_* fields and link composition updated to accept custom ip/port/protocol.
Image processing / hypfer handler
SCR/valetudo_map_parser/hypfer_handler.py
Initialized img_np_array = None; cleared self.img_work_layer after auto-trim/zoom to free memory; reduced severity of an image-creation log (warning→debug); removed some calibration/info logs.
Image utils / bytes handling
SCR/valetudo_map_parser/config/utils.py
Added pil_to_jpeg_bytes helper; _convert_to_binary now supports JPEG bytes when image/jpeg + bytes_format; _process_new_image always populates data from self.shared.to_dict() (no empty dict when bytes_format False).
Project metadata / dependencies
pyproject.toml
Version bumped to 0.2.5b1; dependency bounds updated: numpy >=2.0.0, Pillow >=12.0.0, scipy >=1.17.0 (mvcrender unchanged).
Tests — harness, bytes consistency & profiling
tests/test.py, tests/test_bytes_format_consistency.py
Enabled profiler/memory profiling flags by default, increased FRAME_COUNT to 100, added SAVE_FIRST_FRAME and ENABLE_PIL_SHOW gating for image display. New test tests/test_bytes_format_consistency.py verifies data['image'] populated and size consistency across bytes_format modes.
Tests — calibration overlay
tests/test_autocrop_floor.py
Added draw_calibration_overlay(image, calibration_points, rotation) to render/save annotated calibration overlays for multiple rotations; uses PIL drawing/font.
Tests — minor formatting
tests/test_reload_rotation_change.py
Only formatting change: wrapped asyncio.run calls across multiple lines (no behavior change).
IDE config
.idea/... (.idea/.gitignore, .idea/*.xml, .idea/runConfigurations/*, .idea/*.iml)
Added IntelliJ/PyCharm project files, ignore entries, run configuration, inspection profiles, encodings and module settings — no runtime code changes.

Sequence Diagram(s)

(omitted — changes are primarily API additions, memory/logging tweaks, tests, and config; no new multi-component control flow requiring a sequence diagram)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I hopped through code and left a trail,
exports tidy, links set sail,
docks speak up when not asleep,
images trimmed and buffers keep,
small hops make the project hale.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately reflects the main changes: dependency updates to align with Home Assistant 2025.x and memory optimizations in hypfer_handler for clearing buffers and improving efficiency.
Docstring Coverage ✅ Passed Docstring coverage is 90.24% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
Signed-off-by: SCA075 <82227818+sca075@users.noreply.github.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
SCR/valetudo_map_parser/hypfer_handler.py (1)

458-461: ⚠️ Potential issue | 🟡 Minor

Potential NameError in del statement if calibration loop has zero iterations.

Line 461 does del vacuum_points, map_points, calibration_point, rotation_angle. If vacuum_points or map_points are empty, the for loop at line 458 never executes, leaving calibration_point undefined. The del would then raise NameError.

This is a pre-existing issue (not introduced by this PR), but since you're touching this method, it's worth a minor fix:

Proposed fix
         for vacuum_point, map_point in zip(vacuum_points, map_points):
             calibration_point = {"vacuum": vacuum_point, "map": map_point}
             calibration_data.append(calibration_point)
-        del vacuum_points, map_points, calibration_point, rotation_angle  # free memory.
+        del vacuum_points, map_points, rotation_angle  # free memory.
         return calibration_data
🧹 Nitpick comments (3)
SCR/valetudo_map_parser/rand256_handler.py (1)

117-123: Downgrading image-creation errors to debug may hide actionable failures.

In extract_room_properties, catching RuntimeError | ValueError and logging at debug level means any real data-parsing issue will be invisible at default log levels. Since the function returns None (which the type hint declares as RoomsProperties), callers silently get no rooms. Consider keeping at least a LOGGER.info or LOGGER.warning for the first occurrence, and using debug only for subsequent/expected failures.

That said, the PR description indicates this is an intentional project-wide verbosity reduction and testing confirms it works, so this is a soft observation.

SCR/valetudo_map_parser/hypfer_draw.py (1)

311-315: Minor: dict.get() never raises KeyError — the except block is dead code.

entity_dict.get("charger_location") returns None on missing key rather than raising KeyError. The same applies to entity_dict.get("robot_position") on line 611. These except KeyError blocks are unreachable. This is pre-existing and not introduced by this PR, but worth noting if you're doing a cleanup pass.

SCR/valetudo_map_parser/config/utils.py (1)

120-130: Consider keeping at least warning level for unexpected exceptions in the image pipeline.

These exceptions (ValueError, TypeError, AttributeError, KeyError, RuntimeError) in async_get_image represent genuine failures in the image generation pipeline. Downgrading them to debug means operators won't see them unless they explicitly enable debug logging, making production troubleshooting harder. The graceful fallback to last_image is good, but silent failures can mask regressions.

A middle ground: log at warning but throttle (e.g., only on the first occurrence or every Nth frame).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @.idea/vcs.xml:
- Around line 1-7: Remove IDE config files from VCS and add a .gitignore that
excludes them: create a .gitignore at repo root containing a rule for ".idea/"
(and any other editor files you want ignored), then stop tracking the already
committed IDE files (e.g., .idea/vcs.xml, .idea/misc.xml, .idea/modules.xml) by
running git rm --cached on those paths (do not delete local files), and commit
the .gitignore plus the removal commit with a clear message.

In `@tests/test_autocrop_floor.py`:
- Line 220: The tests call overlay1.show(), overlay2.show(), overlay3.show(),
and overlay4.show() unconditionally which can hang in headless CI; guard each
.show() call with the ENABLE_PIL_SHOW flag used in tests/test.py (e.g., if
ENABLE_PIL_SHOW: overlay1.show()), or remove the calls entirely—update the code
paths that invoke overlay1.show, overlay2.show, overlay3.show, and overlay4.show
to follow that pattern so the display is only executed when ENABLE_PIL_SHOW is
truthy.
- Around line 74-88: Replace the bare except clauses used when loading fonts
with a specific exception type so system-level interrupts aren't swallowed:
update the two try/except blocks that call ImageFont.truetype (the blocks
assigning font and title_font) to catch OSError (or Exception if broader)
instead of a bare except, keeping the fallback to ImageFont.load_default()
unchanged and preserving the behavior of draw.textbbox and draw.text that use
font/title_font.
🧹 Nitpick comments (2)
.idea/runConfigurations/test_py.xml (1)

1-24: IDE-specific run configuration should not be committed.

This .idea/ file is a personal IntelliJ/PyCharm run configuration. Committing it pollutes the repo for contributors using other editors and can cause merge noise. Add .idea/ to .gitignore and remove this file from tracking.

echo ".idea/" >> .gitignore
git rm --cached -r .idea/
tests/test_autocrop_floor.py (1)

75-77: macOS-specific font path will always fall back on Linux/CI.

/System/Library/Fonts/Helvetica.ttc only exists on macOS. On Linux CI or other platforms this silently falls back to load_default(), which may produce different visual output. Consider using a bundled font (the repo already has SCR/valetudo_map_parser/config/fonts/FiraSans.ttf per the test config in tests/test.py Line 342) or making the path configurable.

Also applies to: 85-88

Comment on lines +1 to +7
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project> No newline at end of file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if .gitignore exists and whether .idea is already listed
if [ -f .gitignore ]; then
  echo "=== .gitignore exists ==="
  grep -n '.idea' .gitignore || echo ".idea is NOT in .gitignore"
else
  echo ".gitignore does not exist"
fi

# Check if other .idea files are also tracked
echo ""
echo "=== Checking tracked .idea files ==="
git ls-files '.idea/' 2>/dev/null | head -20

Repository: sca075/Python-package-valetudo-map-parser

Length of output: 395


Remove IDE configuration files and add .gitignore.

.idea/ files are IDE-specific settings that shouldn't be tracked. No .gitignore currently exists in this repository, and multiple .idea/ files are already committed (.idea/vcs.xml, .idea/misc.xml, .idea/modules.xml, etc.). Create a .gitignore file with .idea/ listed and remove these files from version control.

🤖 Prompt for AI Agents
In @.idea/vcs.xml around lines 1 - 7, Remove IDE config files from VCS and add a
.gitignore that excludes them: create a .gitignore at repo root containing a
rule for ".idea/" (and any other editor files you want ignored), then stop
tracking the already committed IDE files (e.g., .idea/vcs.xml, .idea/misc.xml,
.idea/modules.xml) by running git rm --cached on those paths (do not delete
local files), and commit the .gitignore plus the removal commit with a clear
message.

Comment on lines +74 to +88
try:
font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", 12)
except:
font = ImageFont.load_default()

bbox = draw.textbbox((label_x, label_y), label, font=font)
draw.rectangle(bbox, fill="white", outline="black")
draw.text((label_x, label_y), label, fill="black", font=font)

# Add title
title = f"Rotation {rotation}° - Calibration Overlay"
try:
title_font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", 16)
except:
title_font = ImageFont.load_default()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Bare except: clauses — use except Exception: or except OSError:.

Bare except: catches KeyboardInterrupt, SystemExit, etc., which can hide real problems during test runs and make debugging difficult.

Proposed fix
         try:
             font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", 12)
-        except:
+        except OSError:
             font = ImageFont.load_default()
         try:
             title_font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", 16)
-        except:
+        except OSError:
             title_font = ImageFont.load_default()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", 12)
except:
font = ImageFont.load_default()
bbox = draw.textbbox((label_x, label_y), label, font=font)
draw.rectangle(bbox, fill="white", outline="black")
draw.text((label_x, label_y), label, fill="black", font=font)
# Add title
title = f"Rotation {rotation}° - Calibration Overlay"
try:
title_font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", 16)
except:
title_font = ImageFont.load_default()
try:
font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", 12)
except OSError:
font = ImageFont.load_default()
bbox = draw.textbbox((label_x, label_y), label, font=font)
draw.rectangle(bbox, fill="white", outline="black")
draw.text((label_x, label_y), label, fill="black", font=font)
# Add title
title = f"Rotation {rotation}° - Calibration Overlay"
try:
title_font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", 16)
except OSError:
title_font = ImageFont.load_default()
🤖 Prompt for AI Agents
In `@tests/test_autocrop_floor.py` around lines 74 - 88, Replace the bare except
clauses used when loading fonts with a specific exception type so system-level
interrupts aren't swallowed: update the two try/except blocks that call
ImageFont.truetype (the blocks assigning font and title_font) to catch OSError
(or Exception if broader) instead of a bare except, keeping the fallback to
ImageFont.load_default() unchanged and preserving the behavior of draw.textbbox
and draw.text that use font/title_font.

overlay1 = draw_calibration_overlay(image1, cal_points_270, 270)
overlay1.save("/tmp/autocrop_rotation_270_overlay.png")
print(f"✅ Saved Image 1 overlay to /tmp/autocrop_rotation_270_overlay.png")
overlay1.show()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

overlay.show() calls are not gated behind ENABLE_PIL_SHOW.

tests/test.py introduced the ENABLE_PIL_SHOW flag specifically to avoid GUI overhead and headless-environment failures, but this file unconditionally calls .show() on every overlay. This will fail or hang in headless CI and is inconsistent with the approach taken in the main test file.

Proposed fix — guard or remove the `.show()` calls
         overlay1.save("/tmp/autocrop_rotation_270_overlay.png")
         print(f"✅ Saved Image 1 overlay to /tmp/autocrop_rotation_270_overlay.png")
-        overlay1.show()
+        # overlay1.show()  # Use ENABLE_PIL_SHOW or remove for CI compatibility

Apply the same pattern to overlay2.show(), overlay3.show(), and overlay4.show().

Also applies to: 229-229, 238-238, 247-247

🤖 Prompt for AI Agents
In `@tests/test_autocrop_floor.py` at line 220, The tests call overlay1.show(),
overlay2.show(), overlay3.show(), and overlay4.show() unconditionally which can
hang in headless CI; guard each .show() call with the ENABLE_PIL_SHOW flag used
in tests/test.py (e.g., if ENABLE_PIL_SHOW: overlay1.show()), or remove the
calls entirely—update the code paths that invoke overlay1.show, overlay2.show,
overlay3.show, and overlay4.show to follow that pattern so the display is only
executed when ENABLE_PIL_SHOW is truthy.

Add support for custom IP address, port, and protocol configuration for
obstacle image links to enable reverse proxy and HTTPS endpoints.

Changes:
- Add three new configuration constants: CONF_OBSTACLE_LINK_IP,
  CONF_OBSTACLE_LINK_PORT, CONF_OBSTACLE_LINK_PROTOCOL
- Update CameraShared class to accept custom obstacle link parameters
  in device_info
- Modify _compose_obstacle_links() method to use custom parameters with
  fallback to vacuum_ips
- Add validation for port (int conversion) and IP (empty string check)
- Export new constants in __init__.py for mqtt_vacuum_camera integration
- Maintain full backward compatibility (all parameters optional)

Testing:
- Tested with custom endpoint: https://192.168.1.100:180
- Tested fallback behavior with None values
- 10 frames generated successfully, avg 155.9ms
- All 17 obstacle links correctly formatted

Quality:
- Ruff: All checks passed ✅
- Pylint: 9.96/10 (1 warning: too-many-branches in update_shared_data)

Version: 0.2.5b0 (beta release)
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@SCR/valetudo_map_parser/config/shared.py`:
- Around line 174-186: The current guard returns None when vacuum_host_ip is
falsy even if a custom_ip is provided; update the check in the function that
builds obstacle_links (references: obstacle_links, vacuum_host_ip, custom_ip,
custom_protocol, custom_port) so it only returns None when obstacles is falsy OR
both vacuum_host_ip and custom_ip are falsy (e.g., require at least one of
vacuum_host_ip or custom_ip). Also relax the caller-side gating in
generate_attributes (references: generate_attributes, self.vacuum_ips) so it
permits execution when a custom_ip is supplied even if self.vacuum_ips is empty.

Comment on lines 174 to +186
obstacle_links = []
if not obstacles or not vacuum_host_ip:
return None

# Determine which IP/port/protocol to use
ip_to_use = custom_ip if custom_ip else vacuum_host_ip
protocol_to_use = custom_protocol if custom_protocol else "http"

# Build base URL
if custom_port:
base_url = f"{protocol_to_use}://{ip_to_use}:{custom_port}"
else:
base_url = f"{protocol_to_use}://{ip_to_use}"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard on vacuum_host_ip may block valid custom-IP-only configurations.

Line 175 returns None when vacuum_host_ip is falsy, even if custom_ip is provided. Combined with the caller (line 265) gating on self.vacuum_ips, users who configure a custom IP but lack vacuum_ips will never get obstacle links generated. If custom IP is meant to be an independent alternative (not just an override), the guard should also consider custom_ip.

Suggested fix
-        if not obstacles or not vacuum_host_ip:
+        if not obstacles or not (vacuum_host_ip or custom_ip):
             return None

And in generate_attributes, relax the caller guard similarly if needed.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
obstacle_links = []
if not obstacles or not vacuum_host_ip:
return None
# Determine which IP/port/protocol to use
ip_to_use = custom_ip if custom_ip else vacuum_host_ip
protocol_to_use = custom_protocol if custom_protocol else "http"
# Build base URL
if custom_port:
base_url = f"{protocol_to_use}://{ip_to_use}:{custom_port}"
else:
base_url = f"{protocol_to_use}://{ip_to_use}"
obstacle_links = []
if not obstacles or not (vacuum_host_ip or custom_ip):
return None
# Determine which IP/port/protocol to use
ip_to_use = custom_ip if custom_ip else vacuum_host_ip
protocol_to_use = custom_protocol if custom_protocol else "http"
# Build base URL
if custom_port:
base_url = f"{protocol_to_use}://{ip_to_use}:{custom_port}"
else:
base_url = f"{protocol_to_use}://{ip_to_use}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SCR/valetudo_map_parser/config/shared.py` around lines 174 - 186, The current
guard returns None when vacuum_host_ip is falsy even if a custom_ip is provided;
update the check in the function that builds obstacle_links (references:
obstacle_links, vacuum_host_ip, custom_ip, custom_protocol, custom_port) so it
only returns None when obstacles is falsy OR both vacuum_host_ip and custom_ip
are falsy (e.g., require at least one of vacuum_host_ip or custom_ip). Also
relax the caller-side gating in generate_attributes (references:
generate_attributes, self.vacuum_ips) so it permits execution when a custom_ip
is supplied even if self.vacuum_ips is empty.

@sca075 sca075 temporarily deployed to valetudo_map_parser_pypi February 17, 2026 06:07 — with GitHub Actions Inactive
…ization

Fixed API inconsistency where data["image"] structure was only populated
when bytes_format=True, causing KeyError in mqtt_vacuum_camera when
accessing data["image"]["size"] with bytes_format=False.

Added JPEG output format support to enable direct PIL→JPEG conversion
for MJPEG streaming, eliminating the inefficient PIL→PNG→PIL→JPEG
double conversion.

Changes:
- Modified _process_new_image() to always call shared.to_dict()
  (SCR/valetudo_map_parser/config/utils.py:186)
- Updated _convert_to_binary() to support JPEG format via
  shared.image_format (SCR/valetudo_map_parser/config/utils.py:205-221)
- Added pil_to_jpeg_bytes() function for RGBA→RGB→JPEG conversion
  (SCR/valetudo_map_parser/config/utils.py:1001-1006)
- Updated to_dict() docstring to clarify API contract
  (SCR/valetudo_map_parser/config/shared.py:294-308)
- Exported pil_to_jpeg_bytes and pil_to_png_bytes in __init__.py
- Added test_bytes_format_consistency.py to verify API consistency
- Bumped version to 0.2.5b1

Format Support:
- "image/pil" (default): bytes_format=True → PNG bytes
- "image/png": bytes_format=True → PNG bytes
- "image/jpeg": bytes_format=True → JPEG bytes
- All formats: bytes_format=False → PIL Image object

Impact:
- Enables mqtt_vacuum_camera PR #419 MJPEG optimization
- Reduces CPU usage and memory allocations for streaming
- data["image"]["size"] now always available for calibration
- Backward compatible: existing code unchanged

Test: ✅ test_bytes_format_consistency.py passes
@sca075 sca075 temporarily deployed to valetudo_map_parser_pypi February 18, 2026 19:03 — with GitHub Actions Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
SCR/valetudo_map_parser/config/shared.py (1)

76-76: ⚠️ Potential issue | 🟠 Major

binary_image type annotation is out of date — it can now hold a PIL Image.

_convert_to_binary assigns self.shared.binary_image = new_image (a PIL Image) when bytes_format=False, but the annotation still reads bytes | None. The updated to_dict() docstring acknowledges this, but the annotation on the attribute itself misleads type checkers and callers.

📝 Proposed fix
-        self.binary_image: bytes | None = None
+        self.binary_image: bytes | Image | None = None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SCR/valetudo_map_parser/config/shared.py` at line 76, The attribute
self.binary_image is annotated as bytes | None but can also hold a PIL Image;
update the annotation on the SharedConfig (or relevant class) to include the PIL
Image type (e.g., bytes | PIL.Image.Image | None or bytes | Image | None) and
add the appropriate import from PIL (from PIL import Image or from PIL.Image
import Image) so type checkers match the behavior in _convert_to_binary and the
to_dict() docstring.
🧹 Nitpick comments (2)
tests/test_bytes_format_consistency.py (1)

16-16: sys.path manipulation is a test-infrastructure code smell.

Inserting the project root into sys.path at test time indicates the package is not properly installed in the test environment. The canonical fix is to install the package in development mode (pip install -e .) so imports resolve naturally without runtime path hacking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_bytes_format_consistency.py` at line 16, Remove the runtime
sys.path manipulation (the sys.path.insert(...) call) from the tests and stop
relying on test-time path hacks; instead ensure the package is installed in the
test environment (e.g., add a step in your local/CI test setup to run pip
install -e . or configure tox/pytest to install the package in editable mode) so
imports resolve naturally without the sys.path.insert call.
SCR/valetudo_map_parser/config/utils.py (1)

106-109: Stale @param bytes_format docstring.

The docstring says "convert to PNG bytes" but the new _convert_to_binary can also produce JPEG bytes.

📝 Proposed fix
-        `@param` bytes_format: If True, also convert to PNG bytes and store in shared.binary_image
+        `@param` bytes_format: If True, convert to bytes (JPEG or PNG per shared.image_format) and store in shared.binary_image; if False, store the PIL Image directly
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SCR/valetudo_map_parser/config/utils.py` around lines 106 - 109, Update the
stale docstring for the function that documents parameters m_json, destinations,
bytes_format, and text_enabled (the docstring block in utils.py) to reflect that
bytes_format no longer always produces PNG and that _convert_to_binary can
produce JPEG as well; change the bytes_format description to say something like
"If True, also convert to binary image bytes (PNG or JPEG) and store in
shared.binary_image" and ensure any mention of PNG is replaced with a neutral
"binary image bytes" (or lists both PNG/JPEG) so the docstring accurately
matches the behavior of _convert_to_binary.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@SCR/valetudo_map_parser/config/utils.py`:
- Around line 1008-1012: Add a docstring to pil_to_jpeg_bytes matching the
sibling pil_to_png_bytes (e.g., "Convert PIL Image to JPEG bytes.") and ensure
the intermediate RGB image from pil_img.convert("RGB") is explicitly closed to
avoid leaking resources; for example, assign pil_img.convert("RGB") to a local
variable (e.g., rgb_img) and use a try/finally or context manager to save
rgb_img to io.BytesIO and then call rgb_img.close() in the finally block,
keeping the existing signature and behavior of pil_to_jpeg_bytes.

In `@tests/test_bytes_format_consistency.py`:
- Around line 35-42: The device_info dict in the test uses wrong keys so
update_shared_data cannot read them; change the test's keys to match the
constants used by update_shared_data (use ATTR_ROTATE/"rotate_image" instead of
"image_rotate", CONF_AUTO_ZOOM/"auto_zoom" instead of "image_auto_zoom", and
keep CONF_ASPECT_RATIO/"aspect_ratio"), so that update_shared_data(device_info)
reads the intended values rather than falling back to DEFAULT_VALUES; locate the
device_info declaration in tests/test_bytes_format_consistency.py and rename the
keys accordingly.
- Line 26: The test function test_bytes_format_consistency is defined as async
but lacks the pytest-asyncio marker and the project lacks pytest-asyncio as a
dev dependency; add pytest-asyncio to dev dependencies (e.g., under
[tool.poetry.group.dev.dependencies]) and annotate the test with
`@pytest.mark.asyncio` above async def test_bytes_format_consistency() so pytest
runs it as an async test.

---

Outside diff comments:
In `@SCR/valetudo_map_parser/config/shared.py`:
- Line 76: The attribute self.binary_image is annotated as bytes | None but can
also hold a PIL Image; update the annotation on the SharedConfig (or relevant
class) to include the PIL Image type (e.g., bytes | PIL.Image.Image | None or
bytes | Image | None) and add the appropriate import from PIL (from PIL import
Image or from PIL.Image import Image) so type checkers match the behavior in
_convert_to_binary and the to_dict() docstring.

---

Duplicate comments:
In `@SCR/valetudo_map_parser/config/shared.py`:
- Around line 174-175: The guard that skips obstacle link creation incorrectly
bails when vacuum_host_ip is falsy even though a user-specified obstacle_link_ip
or custom_ip should allow links; change the condition around obstacle_links (and
any early return) to check for the presence of obstacle_link_ip or
custom_ip/custom_port/custom_protocol or vacuum_host_ip instead of just `not
vacuum_host_ip`, and update generate_attributes (which currently gates on
self.vacuum_ips) to also accept and use obstacle_link_ip/custom_ip when
self.vacuum_ips is empty so obstacle links are produced even for custom-IP-only
deployments (refer to obstacle_links, vacuum_host_ip, obstacle_link_ip,
custom_ip/custom_port/custom_protocol, generate_attributes, and
self.vacuum_ips).

---

Nitpick comments:
In `@SCR/valetudo_map_parser/config/utils.py`:
- Around line 106-109: Update the stale docstring for the function that
documents parameters m_json, destinations, bytes_format, and text_enabled (the
docstring block in utils.py) to reflect that bytes_format no longer always
produces PNG and that _convert_to_binary can produce JPEG as well; change the
bytes_format description to say something like "If True, also convert to binary
image bytes (PNG or JPEG) and store in shared.binary_image" and ensure any
mention of PNG is replaced with a neutral "binary image bytes" (or lists both
PNG/JPEG) so the docstring accurately matches the behavior of
_convert_to_binary.

In `@tests/test_bytes_format_consistency.py`:
- Line 16: Remove the runtime sys.path manipulation (the sys.path.insert(...)
call) from the tests and stop relying on test-time path hacks; instead ensure
the package is installed in the test environment (e.g., add a step in your
local/CI test setup to run pip install -e . or configure tox/pytest to install
the package in editable mode) so imports resolve naturally without the
sys.path.insert call.

_LOGGER = logging.getLogger(__name__)


async def test_bytes_format_consistency():
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for asyncio configuration in common config files
echo "=== Checking pyproject.toml for asyncio config ==="
rg -A 5 "asyncio_mode|pytest-asyncio" pyproject.toml 2>/dev/null || echo "No asyncio config found"

echo -e "\n=== Checking pytest.ini ==="
cat pytest.ini 2>/dev/null || echo "No pytest.ini found"

echo -e "\n=== Checking setup.cfg ==="
cat setup.cfg 2>/dev/null || echo "No setup.cfg found"

echo -e "\n=== Checking conftest.py ==="
fd "conftest.py" --exec cat {} \; 2>/dev/null || echo "No conftest.py found"

echo -e "\n=== Listing test files ==="
fd "test_bytes_format_consistency.py" --exec cat {} \;

Repository: sca075/Python-package-valetudo-map-parser

Length of output: 5509


🏁 Script executed:

#!/bin/bash
# Check dependencies in pyproject.toml
echo "=== Checking for pytest-asyncio in dependencies ==="
rg -A 30 "\[project\]" pyproject.toml | head -60
rg -A 50 "dependencies|dev-dependencies|optional-dependencies" pyproject.toml

Repository: sca075/Python-package-valetudo-map-parser

Length of output: 968


Add @pytest.mark.asyncio decorator and pytest-asyncio to dev dependencies.

async def test_bytes_format_consistency() has no asyncio marker. The project also lacks pytest-asyncio in dev dependencies. Without both, pytest will collect but not properly execute this coroutine as an async test; it runs only via the if __name__ == "__main__" path.

Proposed fix
  1. Add pytest-asyncio to dev dependencies in pyproject.toml:
[tool.poetry.group.dev.dependencies]
pytest-asyncio = "*"
  1. Add the decorator to the test:
+import pytest
+
+@pytest.mark.asyncio
 async def test_bytes_format_consistency():
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_bytes_format_consistency.py` at line 26, The test function
test_bytes_format_consistency is defined as async but lacks the pytest-asyncio
marker and the project lacks pytest-asyncio as a dev dependency; add
pytest-asyncio to dev dependencies (e.g., under
[tool.poetry.group.dev.dependencies]) and annotate the test with
`@pytest.mark.asyncio` above async def test_bytes_format_consistency() so pytest
runs it as an async test.

Comment on lines +35 to +42
device_info = {
"image_rotate": 0,
"image_auto_zoom": True,
"image_ref_height": 1080,
"image_ref_width": 1920,
"user_language": "en",
"aspect_ratio": "None",
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the constants and their values
rg -n "ATTR_ROTATE|CONF_ASPECT_RATIO|CONF_AUTO_ZOOM" --type py -B 2 -A 0 | head -50

Repository: sca075/Python-package-valetudo-map-parser

Length of output: 2822


🏁 Script executed:

#!/bin/bash
# Also search for where these constants are defined
rg -n "(ATTR_ROTATE|CONF_ASPECT_RATIO|CONF_AUTO_ZOOM)\s*=" --type py

Repository: sca075/Python-package-valetudo-map-parser

Length of output: 284


🏁 Script executed:

#!/bin/bash
# Find the test file and examine its full context
find . -name "test_bytes_format_consistency.py" -type f

Repository: sca075/Python-package-valetudo-map-parser

Length of output: 125


🏁 Script executed:

#!/bin/bash
# Look for update_shared_data function to understand what it expects
rg -n "def update_shared_data" --type py -A 30

Repository: sca075/Python-package-valetudo-map-parser

Length of output: 2824


🏁 Script executed:

#!/bin/bash
# Get the full test file content
cat -n ./tests/test_bytes_format_consistency.py | head -80

Repository: sca075/Python-package-valetudo-map-parser

Length of output: 3541


🏁 Script executed:

#!/bin/bash
# Get the complete update_shared_data function
rg -n "def update_shared_data" --type py -A 50

Repository: sca075/Python-package-valetudo-map-parser

Length of output: 4623


🏁 Script executed:

#!/bin/bash
# Also check for any other constants that might be used
rg -n "CONF_|ATTR_" SCR/valetudo_map_parser/const.py --type py | head -40

Repository: sca075/Python-package-valetudo-map-parser

Length of output: 1668


🏁 Script executed:

#!/bin/bash
# Check CameraSharedManager init method
rg -n "class CameraSharedManager" --type py -A 20

Repository: sca075/Python-package-valetudo-map-parser

Length of output: 1791


🏁 Script executed:

#!/bin/bash
# Check the __init__ method of CameraSharedManager
rg -n "def __init__" SCR/valetudo_map_parser/config/shared.py -A 15 | head -60

Repository: sca075/Python-package-valetudo-map-parser

Length of output: 1477


Device info keys don't match the constants expected by update_shared_data.

update_shared_data reads configuration via ATTR_ROTATE = "rotate_image", CONF_AUTO_ZOOM = "auto_zoom", and CONF_ASPECT_RATIO = "aspect_ratio". The test's device_info dict uses mismatched keys: "image_rotate" (should be "rotate_image"), "image_auto_zoom" (should be "auto_zoom"), and "aspect_ratio" (correct). When device_info.get(constant) is called in update_shared_data, the mismatched keys aren't found, so the function falls back to DEFAULT_VALUES, causing the test to pass with unintended default values rather than the test-provided config.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_bytes_format_consistency.py` around lines 35 - 42, The device_info
dict in the test uses wrong keys so update_shared_data cannot read them; change
the test's keys to match the constants used by update_shared_data (use
ATTR_ROTATE/"rotate_image" instead of "image_rotate", CONF_AUTO_ZOOM/"auto_zoom"
instead of "image_auto_zoom", and keep CONF_ASPECT_RATIO/"aspect_ratio"), so
that update_shared_data(device_info) reads the intended values rather than
falling back to DEFAULT_VALUES; locate the device_info declaration in
tests/test_bytes_format_consistency.py and rename the keys accordingly.

Add support for configurable image output formats (PIL, PNG, JPEG) with proper API:

- Add set_content_type() and get_content_type() methods to CameraShared
  - Validates format against ALLOWED_IMAGE_FORMAT constant
  - Makes _image_format private attribute for proper encapsulation

- Implement match-case logic in _convert_to_binary() for all formats
  - image/jpeg → pil_to_jpeg_bytes() with RGB conversion
  - image/png → pil_to_png_bytes()
  - image/pil → pil_to_pil_bytes()

- Add ALLOWED_IMAGE_FORMAT constant to const.py
  - Centralized format definitions: pil, png, jpeg
  - Exported in __init__.py for mqtt_vacuum_camera usage

- Add pil_to_pil_bytes() conversion function
  - Completes trio of image conversion utilities
  - All three functions exported in __init__.py

- Bump version: 0.2.5b1 → 0.2.5b2

This API is for programmer use in mqtt_vacuum_camera, not end-user configuration.
@sca075 sca075 temporarily deployed to valetudo_map_parser_pypi February 19, 2026 19:36 — with GitHub Actions Inactive
- Added automatic content type selection in _convert_to_binary():
  * MAP_VIEW mode → JPEG format (optimized for map images)
  * OBSTACLE_VIEW mode → PNG format (preserves obstacle details)

- Implementation is library-safe:
  * Works with CameraModes constants (CameraModes.MAP_VIEW)
  * Works with direct string values ("map_view")
  * Added CameraModes to utils.py imports

- Version bump: 0.2.5b2 → 0.2.5b3
  * Updated pyproject.toml
  * Updated __init__.py

Quality checks:
- Ruff: All checks passed!
- isort: All imports properly sorted
- Pylint: 9.98/10 (no new issues)
@sca075 sca075 temporarily deployed to valetudo_map_parser_pypi February 21, 2026 10:30 — with GitHub Actions Inactive
Version Updates:
- Bumped from 0.2.5b3 to stable 0.2.5
- Updated pyproject.toml and __init__.py

CHANGELOG Updates:
- Added comprehensive entries for all beta versions:
  * 0.2.5b0: Customizable obstacle image links
  * 0.2.5b1: JPEG format support and API consistency fixes
  * 0.2.5b2: Image format configuration with ALLOWED_IMAGE_FORMAT
  * 0.2.5b3: Reverted automatic content type selection
  * 0.2.5: Stable release

Code Changes:
- Reverted experimental automatic content type selection from utils.py
- Removed CameraModes import (no longer needed)
- Content type selection remains manual via set_content_type() method
- Code formatted with ruff

Quality:
- isort: All imports properly sorted
- ruff: Code formatted
- Library maintains backward compatibility
@sca075 sca075 merged commit 75904f4 into main Feb 21, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant