17 β Map Manager¶
Scope: Complete implementation reference for
maps/map_manager.py. Every function signature, storage path, and behavior is derived directly from the source. A developer should be able to re-implement the map manager from this document alone.
1. Overview¶
maps/map_manager.py is a collection of pure functions with no class, no state, and no async operations. Every function is keyword-only (all parameters after *) and takes data (the integration's live storage dict), operating on data["maps"] directly.
All mutations are in-place on the data dict. The caller is responsible for calling manager.async_save() after mutating.
Module: custom_components/eufy_vacuum/maps/map_manager.py
2. Map Bucket Schema¶
The canonical unit is a map bucket β a dict stored at data["maps"][vacuum_entity_id][str(map_id)]. The bucket is a union of two concerns that happen to share the same per-map key:
- Map management (owned by
maps/map_manager.py) βmap_id,metadata,rooms,summary. - Image analysis + map UI state (written by external handlers, primarily
mapping/mapping_services.py) βimage_segments,custom_segments,custom_layouts,active_custom_layout_id,segmentation_mode,image_segment_adjustments,image_variants,segment_room_links,companion_anchors,saved_zones,hidden_regions,area_label_anchors,live_map_rotation,overlay_visibility.
map_manager.py only ever touches the first group; it never reads or initialises the image/UI-state keys. They are listed here because they live in the same bucket and any code that walks data["maps"] (delete protection, debug dumps) will encounter them.
{
"map_id": str, # the map ID, always stored as str(map_id)
"metadata": {
"last_discovery": dict, # {active_map_id, room_count} from save_map_discovery_snapshot
"discovered_rooms": list, # raw discovered room list from the snapshot
"last_rebuild": dict, # {map_id, room_count, preserve_existing_settings} from rebuild_map_bucket
},
"rooms": dict[str, dict], # room_id_str β managed room dict
"summary": dict, # last-written summary snapshot
# --- image-analysis / map-UI-state keys (written by mapping/mapping_services.py;
# NOT managed by map_manager.py, NOT pre-initialised by ensure_map_bucket) ---
"image_segments": dict, # canonical CV SegmentationResult cache (the
# base/"cv" segment store). Written by
# analyze_map_image; {available, analyzed_at,
# image, segments, summary, ...}.
"custom_segments": dict, # LEGACY single user-authored no-CV segment store
# (replace-all). Same shape as image_segments:
# {available, engine:"custom", analyzed_at,
# image:{width,height,variant:"custom"}, segments,
# summary}. Migrated lazily + non-destructively into a
# "Custom" entry under custom_layouts (kept, never
# deleted) β see Β§10 of 11-mapping-system.
"custom_layouts": dict, # {layout_id: layout}; the named multi-custom-layout
# collection. Each layout owns everything per-layout:
# {id, name, backdrop_variant, backdrop_source?,
# custom_segments, segment_room_links,
# companion_anchors, created_at, updated_at}.
# Optional backdrop_source:"live" pins the layout to
# the brand's live-map image entity as its backdrop
# (the card's "Live map" source) instead of an
# uploaded custom_<id> variant; absent for normal
# layouts. Set by create_custom_layout's
# backdrop_source param (_create_layout) and surfaced
# in the get_map_segments layout summary. CRUD + lazy
# legacy migration (_migrate_custom_layouts) live in
# mapping_services.py.
"active_custom_layout_id": str, # id of the layout served in "custom" mode, or None.
"segmentation_mode": str, # "cv" | "custom"; pointer that selects which of the
# two segment stores get_map_segments serves.
# Defaults to "cv" when absent. set_segmentation_mode
# only flips this flag β it NEVER re-runs the segmenter.
"image_segment_adjustments": { # per-segment manual edits to CV segments, keyed by
# segment_id; applied to polygons at read time.
"<segment_id>": {
"offset_x": int, # whole-shape translation
"offset_y": int,
"edge_left": int, # per-edge nudge (10% band each side)
"edge_right": int,
"edge_top": int,
"edge_bottom": int,
"vertex_moves": [ # individual vertex deltas
{"index": int, "delta_x": int, "delta_y": int},
],
},
},
"image_variants": { # uploaded backdrop images, keyed by variant name.
# Variant β {default, dark, light, custom}. dark/
# light/default feed the segmenter; "custom" is the
# no-CV authoring backdrop and is never segmented β
# its width/height are the px space set_custom_segments
# rasterises against.
"<variant>": {
"variant": str, # echoes the key
"path": str, # on-disk PNG path
"browser_url": str, # /eufy_vacuum/maps/<object_id>/map_<map_id><suffix>.png
"width": int, # measured pixel dims (PIL), or declared fallback
"height": int,
},
},
"segment_room_links": dict[str, str], # {segment_id: room_id}; user-assigned 1:1
# segmentβroom mapping. Injected as a
# per-segment room_id field at read time.
"companion_anchors": { # {room_id | "dock": {pct_x, pct_y}} companion-sprite
# anchor positions, 0-100 % from the image top-left.
# The reserved "dock" key is a map-level spot the
# docked/idle mascot homes to (NOT a room).
"<room_id|'dock'>": {"pct_x": float, "pct_y": float},
},
"saved_zones": { # {zone_id: zone} named reusable clean regions
# (draw-a-box saved zones). Written by
# _create_saved_zone / _migrate_saved_zones.
"<zone_id>": {
"id": str,
"name": str, # user label; "Zone" when blank
"geometry": list, # [[x, y], ...] normalized 0-1 point list (the
# zone shape; dispatch works off it directly)
"area_m2": float, # or None until computed (Wave 2)
"room_number": int, # filing bucket, or None until computed (Wave 2)
"kind": str, # default "clean"
"created_at": str, # iso
"updated_at": str, # iso
},
},
"hidden_regions": list, # [[x0, y0, x1, y1], ...] normalized 0-1 top-left-
# origin mask rects the card draws to hide map
# noise. Replace-all; each entry sanitized (4
# finite numbers, clamped 0-1, ordered min<max,
# degenerate dropped). Written by
# _handle_set_hidden_regions.
"area_label_anchors": { # {room_id: {pct_x, pct_y}} dragged position of a
# room's mΒ² label chip, 0-100 % of the map content
# box. Written by _handle_set_area_label_anchor
# (null both to reset to room centre).
"<room_id>": {"pct_x": float, "pct_y": float},
},
"live_map_rotation": int, # display-only live-map rotation β {0, 90, 180, 270}.
# Never affects cleaning/dispatch. Written by
# _handle_set_live_map_rotation.
"overlay_visibility": dict, # {layer: bool} partial delta map β stores only the
# user's overrides, merged over defaults at read
# time via resolve_overlay_visibility; reset:true
# clears it. Written by
# _handle_set_map_overlay_visibility.
}
Metadata keys are written by save_map_discovery_snapshot() (last_discovery, discovered_rooms) and rebuild_map_bucket() (last_rebuild). There is no display_name or discovered_at field.
The image/UI-state keys are documented in full in 11-mapping-system; their derived read-time fields (
polygon_pct, injectedroom_id, appliedadjustments) are computed bymapping/mapping_services.py::_handle_get_map_segments, not stored.
3. Functions¶
3.1 ensure_map_bucket¶
Creates the map bucket at data["maps"][vacuum_entity_id][str(map_id)] if it does not already exist. Returns the (possibly newly created) bucket dict.
Default shape on creation:
Idempotent β safe to call even if the bucket already exists.
Image/UI-state keys are not pre-initialised.
ensure_map_bucket()writes onlymap_id,metadata,rooms, andsummary. The image-analysis / map-UI-state keys (image_segments,custom_segments,custom_layouts,active_custom_layout_id,segmentation_mode,image_segment_adjustments,image_variants,segment_room_links,companion_anchors,saved_zones,hidden_regions,area_label_anchors,live_map_rotation,overlay_visibilityβ see Β§2) are written on demand by the external handlers inmapping/mapping_services.py, each of which callsensure_map_bucket()and thensetdefault()s the key it owns. (custom_layouts/active_custom_layout_idare seeded by_migrate_custom_layouts(), which the layout-CRUD handlers run before mutating; legacycustom_segmentsis migrated lazily into a layout β see Mapping system Β§10.) Consumers must therefore read these viabucket.get(key) or {}rather than assuming presence.
3.2 get_map_bucket¶
Returns the existing bucket at data["maps"][vacuum_entity_id][str(map_id)], or an empty default-shape dict if not found. Does not create the bucket in storage β read-only.
3.3 save_map_discovery_snapshot¶
save_map_discovery_snapshot(
*,
data: dict,
vacuum_entity_id: str,
map_id: str,
discovery_payload: dict,
) -> dict
Calls ensure_map_bucket() first, then writes two metadata keys derived from discovery_payload:
bucket["metadata"]["last_discovery"] = {
"active_map_id": discovery_payload.get("active_map_id"),
"room_count": discovery_payload.get("room_count", 0),
}
bucket["metadata"]["discovered_rooms"] = discovery_payload.get("rooms", [])
Returns the map bucket. It does not assign discovery_payload directly to metadata.
3.4 rebuild_map_bucket¶
rebuild_map_bucket(
*,
data: dict,
vacuum_entity_id: str,
map_id: str,
discovered_rooms: list[dict],
preserve_existing_settings: bool = True,
) -> dict
Rebuilds the managed rooms in the bucket from a fresh discovery list:
- Calls
ensure_map_bucket(). - Reads existing rooms from the bucket.
- Builds each managed room inline (1-indexed
order), carrying over prior settings whenpreserve_existing_settings=True. (It does not callroom_manager.build_managed_rooms().) - Writes the rebuilt rooms to
bucket["rooms"], setsbucket["metadata"]["last_rebuild"], and writesbucket["summary"](enabled/disabled counts + sorted enabled/disabled room lists).
Returns a summary dict (not the bucket):
{
"vacuum_entity_id": str,
"map_id": str,
"room_count": int,
"rooms": dict[str, dict], # the rebuilt rooms
"summary": dict, # the bucket summary
"metadata": dict, # the bucket metadata
}
When preserve_existing_settings=True (default), user settings (fan speed, clean mode, floor type, etc.) are preserved for rooms that still exist in the discovery list. New rooms get safe defaults. floor_type encodes carpet pile height in the value itself (e.g. "carpet_low_pile"); there is no separate carpet_type field.
When preserve_existing_settings=False, all rooms are re-initialized with defaults β used for full reset flows.
3.5 get_vacuum_maps_summary¶
Returns a dict wrapping a list of per-map summaries (maps sorted by str(map_id)):
{
"vacuum_entity_id": str,
"map_count": int,
"maps": [
{
"map_id": str,
"room_count": int,
"enabled_room_count": int, # from summary.enabled_count
"disabled_room_count": int, # from summary.disabled_count
"last_discovery": dict, # from metadata.last_discovery
},
...
],
}
There is no display_name field. Maps with empty rooms dicts are not excluded β every map bucket is reported.
4. Storage Path Reference¶
| Path | Type | Description |
|---|---|---|
data["maps"] |
dict | Top-level map storage, keyed by vacuum_entity_id |
data["maps"][vacuum_entity_id] |
dict | All maps for one vacuum, keyed by str(map_id) |
data["maps"][vacuum_entity_id][str(map_id)] |
dict | One map bucket |
data["maps"][vacuum_entity_id][str(map_id)]["rooms"] |
dict | Managed rooms, keyed by str(room_id) |
data["maps"][vacuum_entity_id][str(map_id)]["metadata"] |
dict | Discovery snapshot + display metadata |
data["maps"][vacuum_entity_id][str(map_id)]["summary"] |
dict | Last written summary snapshot |
The following keys live in the same bucket but are written by mapping/mapping_services.py, not by map_manager.py (none are created by ensure_map_bucket() β see Β§3.1):
| Path | Type | Description |
|---|---|---|
β¦[str(map_id)]["image_segments"] |
dict | CV segmentation cache (base "cv" store). Written by analyze_map_image |
β¦[str(map_id)]["custom_segments"] |
dict | Legacy single user-authored no-CV segment store (replace-all). Migrated lazily + non-destructively into a "Custom" entry under custom_layouts β see Mapping system Β§10 |
β¦[str(map_id)]["custom_layouts"] |
dict | {layout_id: {id, name, backdrop_variant, backdrop_source?, custom_segments, segment_room_links, companion_anchors, created_at, updated_at}} named multi-custom-layout collection (each layout owns its own backdrop/segments/links/anchors). Optional backdrop_source:"live" pins the layout to the brand's live-map image entity (the card's "Live map" source) instead of an uploaded custom_<id> variant β absent for normal layouts; set by create_custom_layout's backdrop_source param, surfaced in the get_map_segments layout summary. Seeded by _migrate_custom_layouts, CRUD by the layout handlers |
β¦[str(map_id)]["active_custom_layout_id"] |
str | None | Id of the layout served in "custom" mode, or None. Seeded by _migrate_custom_layouts |
β¦[str(map_id)]["segmentation_mode"] |
str | "cv" | "custom"; selects which segment store get_map_segments serves. Default "cv". Written by set_segmentation_mode (flag flip only) |
β¦[str(map_id)]["image_segment_adjustments"] |
dict | {segment_id: {offset_x, offset_y, edge_left/right/top/bottom, vertex_moves:[{index,delta_x,delta_y}]}} manual CV-segment edits. Written by adjust_map_segment |
β¦[str(map_id)]["image_variants"] |
dict | {variant: {variant, path, browser_url, width, height}} uploaded backdrops, variant β default/dark/light/custom. Written by upload_map_image, pruned by delete_map_image |
β¦[str(map_id)]["segment_room_links"] |
dict | {segment_id: room_id} 1:1 segmentβroom links. Written by set_segment_room_link |
β¦[str(map_id)]["companion_anchors"] |
dict | {room_id\|"dock": {pct_x, pct_y}} sprite anchors (0-100 %); reserved "dock" key is a map-level mascot spot. Written by set_companion_anchor |
β¦[str(map_id)]["saved_zones"] |
dict | {zone_id: {id, name, geometry, area_m2, room_number, kind, created_at, updated_at}} named reusable clean regions; geometry is a normalized 0-1 point list, kind defaults "clean", area_m2/room_number are None until computed. Seeded by _migrate_saved_zones, written by _create_saved_zone |
β¦[str(map_id)]["hidden_regions"] |
list | [[x0, y0, x1, y1], β¦] normalized 0-1 top-left-origin mask rects that hide map noise (replace-all; sanitized β finite, clamped 0-1, ordered min<max, degenerate dropped). Written by _handle_set_hidden_regions |
β¦[str(map_id)]["area_label_anchors"] |
dict | {room_id: {pct_x, pct_y}} dragged mΒ² label positions (0-100 % of the map content box); null both to reset to room centre. Written by _handle_set_area_label_anchor |
β¦[str(map_id)]["live_map_rotation"] |
int | Display-only live-map rotation β {0, 90, 180, 270}; never affects cleaning/dispatch. Written by _handle_set_live_map_rotation |
β¦[str(map_id)]["overlay_visibility"] |
dict | {layer: bool} partial delta map storing only user overrides (merged over defaults at read time via resolve_overlay_visibility; reset:true clears it). Written by _handle_set_map_overlay_visibility |
5. Integration Points¶
| Caller | Function | When |
|---|---|---|
rooms/room_crud.py |
ensure_map_bucket(), rebuild_map_bucket() |
save_managed_rooms(), rebuild_map() |
rooms/room_crud.py |
get_map_bucket(), get_vacuum_maps_summary() |
get_managed_rooms(), get_managed_maps_summary() |
core/manager.py |
get_map_bucket() |
queue and room-clean payload builds |
rooms/access_graph.py, profiles/manager.py |
get_map_bucket(), ensure_map_bucket() |
automation-metadata reads, room/run-profile reads |
setup/delete.py |
reads data["maps"] directly |
map-delete protection evaluation |
save_map_discovery_snapshot()has no current caller β it writes thelast_discovery/discovered_roomsmetadata keys (see Β§3.3) but the live discovery path (rooms/room_crud.py::discover_rooms()) caches intodata["discovery"]directly instead.See also: 15-setup-system Β§3 for the
import_active_mapworkflow that callsensure_map_bucket()andrebuild_map_bucket(); 08-rooms-system Β§5 forRoomMapManager(rooms/room_crud.py) which reads and writes the rooms dict inside each map bucket.