Add possibility to per-map override a Custom Floor Plan - #42
Conversation
|
@Lash-L some weird shenanigans seem to be happening on your repositories. This PR seems to have somehow fallen in the cracks, and it doesn't display on the open PR list. There is in fact only one PR there, despite the number next to PRs still claming otheriwse. Please confirm that you still have visibility into this issue, and that I don't have to reopen it. There is no urgency to you reviewing the code. I just need a quick "I see you, I'll get to this eventually" ;) |
|
Haha I knew this PR existed! Was confused when I didn't see it. My account was false flagged banned. I got my account back but there's some weirdness. I see it! |
|
No worries. Lemme know when you will want to discuss anything. At your pace. Cheers :) |
Lash-L
left a comment
There was a problem hiding this comment.
Just a few comments stick out to me!
| rot_w, rot_h, offset_x, offset_y, ovr_w, ovr_h | ||
| ) | ||
|
|
||
| canvas = Image.new("RGBA", (canvas_w, canvas_h), _TRANSPARENT) |
There was a problem hiding this comment.
This can blow up memory 16,384 × 16,384 is about 1GB. Add a total pixel limit too keep things safe
| ) | ||
|
|
||
| canvas = Image.new("RGBA", (canvas_w, canvas_h), _TRANSPARENT) | ||
| canvas.paste(bg, (round(offset_x - origin_x), round(offset_y - origin_y)), bg) |
There was a problem hiding this comment.
Please use canvas.alpha_composite(bg, dest=(...)), so that semi transparent floor plans/ webP will work
There was a problem hiding this comment.
Pull request overview
This PR adds per-map Custom Floor Plan overrides to the Roborock Custom Map integration, enabling users to upload a floor plan image per map and interactively tune its placement (offset/scale) with a live preview, while keeping calibration aligned for the Xiaomi map card.
Changes:
- Introduces an options flow UI for per-map upload/adjust/remove of a Custom Floor Plan, including a live preview via websocket.
- Adds a background-override rendering pipeline that composites the custom floor plan under Roborock drawables while preserving calibration alignment across map crop drift.
- Refactors rotation storage/signaling to support being updated from the options flow as well as the select entity.
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Updates documentation to describe the new Custom Floor Plan workflow and bumps the minimum HA version note. |
| custom_components/roborock_custom_map/translations/en.json | Adds English UI strings for the new options flow steps and error/abort messages. |
| custom_components/roborock_custom_map/translations/de.json | Adds German UI strings for the new options flow steps and error/abort messages. |
| custom_components/roborock_custom_map/select.py | Updates rotation handling to use shared key helpers and to accept rotation changes triggered by the options flow. |
| custom_components/roborock_custom_map/preview.py | Adds preview session storage and a websocket command to support near-real-time flow previews. |
| custom_components/roborock_custom_map/map_render.py | Adds synchronous (executor) rendering utilities for compositing the custom floor plan with map overlays/masks and validating uploads. |
| custom_components/roborock_custom_map/manifest.json | Adds dependencies needed for file upload + websocket preview and bumps integration version. |
| custom_components/roborock_custom_map/image.py | Extends map image entity to render the composite (preview/saved) and adjust calibration points accordingly, with caching. |
| custom_components/roborock_custom_map/const.py | Adds new constants and shared helpers (keys, signals, runtime rotation storage, override scale parsing). |
| custom_components/roborock_custom_map/config_flow.py | Implements the options flow steps (select map, upload, tune, remove) and persists override placement anchored in vacuum coordinates. |
| custom_components/roborock_custom_map/init.py | Adds update listener-based refresh for changed overrides, preview session storage initialization, and cleanup of stored override files. |
| .gitignore | Adds __pycache__/ to gitignore. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| rotation = self._get_rotation(preview) | ||
| topleft = self._override_topleft(map_content, rotation, preview) | ||
| if topleft is not None and self._override_render_ok is not False: | ||
| transform = map_render.MapTransform.from_dimensions( |
| bg = Image.open(io.BytesIO(bg_bytes)) | ||
| bg = ImageOps.exif_transpose(bg).convert("RGBA") |
| process_uploaded_file consumes the temp file, so re-submitting the same | ||
| (already consumed) id raises ValueError; treat that as an invalid image | ||
| so the user is simply asked to pick the file again. A failed read of | ||
| the temp file surfaces as a form error instead of crashing the flow. | ||
| """ | ||
| try: | ||
| with process_uploaded_file(self.hass, file_id) as path: | ||
| data = path.read_bytes() | ||
| except ValueError as err: | ||
| raise map_render.InvalidBackgroundImage("invalid_image") from err | ||
| except OSError as err: | ||
| raise map_render.InvalidBackgroundImage("upload_failed") from err |
Hey follow-up to: #16
There are two alternative solutions to the same problem currently "hanging".
However, based on my personal experience I believe a more user-friendly approach is required. Especially one that is resilient to the map inevitably drifting with time. One which allows the User to manually self-correct at any moment. Preferably from the UI. While supporting multiple floors.
Given HAs limitations - this is the most comprehensive flow that I was able to come up with (safe for something crazy to maintain like a full-blown JS custom card):
Upload Screen
Adjustment Screen
Removal Screen
Full Disclosure
I am no "Pythonista". Nor am I very aware of the idioms of HAs development. I leaned on an LLM quite a bit during development, and it did end up replacing in the end quite a bit of my initial draft. Having said that. I AM a professional SE. I did know what to prompt, what to check, and did so meticulously. This entire effort took a few solid days over many iterations / bugfixes.
Cheers,
Adam