Skip to content

Repository files navigation

AnyVac (companion integration)

Companion Home Assistant integration for the AnyVac card.

The official Roborock integration already parses the vacuum map into structured data — robot position, cleaning path, room geometry and calibration points — but it only renders that to a PNG image entity and never exposes the structured data. AnyVac reads that already-parsed data out of the Roborock integration and re-publishes it, so the AnyVac card can draw the robot and its path on a custom floorplan and run zone / pin-and-go cleaning with no manual calibration.

Requirements

  • The official Roborock integration set up and working (AnyVac reads from it; it does not open its own Roborock connection).
  • Home Assistant 2024.1.0 or newer.

Install

  1. Add this repository to HACS as a custom repository (category: Integration).
  2. Install AnyVac and restart Home Assistant.
  3. Add the AnyVac integration (Settings → Devices & services → Add integration). There is nothing to configure — it discovers your Roborock vacuums automatically.

What it exposes

For each Roborock vacuum, a sensor (e.g. sensor.<vacuum>_anyvac_map) whose state is the current path-point count and whose attributes carry the map payload. The card consumes the pixel-space attributes below (schema_version: 2) — no calibration or mm math on the card side:

Attribute Meaning
schema_version 2 once the integration's own 3-point affine solve succeeds; the card shows a degraded-mode banner and disables smart features below this
vacuum_position_px / charger_px {x, y} robot / dock position, in rendered image pixels
path_dry_px dry-clean trajectory, as segments (list of point-lists — one contiguous run per segment, so gaps from transit/mop-wash aren't bridged with a straight line)
path_wet_px mop trajectory, flat list of {x, y} pixels
rooms [{segment_id, name, bbox_px:{x0,y0,x1,y1}, pos_x, pos_y, estimate_dry, estimate_wet, progress_pct}, …]
rooms_estimate learned per-room clean-time estimates in minutes: {room: {dry, wet}} (docs/16 continuous calibration)
rooms_progress per-room debug progress: {room: {spatial_pct, visited_cells, total_cells, time_pct, elapsed_s, est_s}}
rooms_last_cleaned per-room last-cleaned info by clean type (mirrors the timestamp sensors below)
room_sequence / room_pins / selected_rooms / view_layers orchestration/UI state the card reads and writes via services below
floorplan_seats Visual editor manual seat + appearance + per-room + room-style override layer (docs/41 + docs/42), keyed by floorplan image_base.src — set/cleared via anyvac.set_floorplan_seat, merged over config by the card
pipeline_ok / pipeline_error integration self-diagnostic for the current poll
duid, calib_debug, transit_cells diagnostics — device id, calibration solve debug info, "seen but not counted" cells outside the active job's room scope

Home frame (multi-vacuum, schema_version: 3)

schema_version is now 3 — purely additive, every attribute above is unchanged. When two or more vacuums share the same physical space, AnyVac automatically works out how their maps line up (docs/40) and republishes the SAME geometry above in one shared coordinate space, so a floorplan built once (via anyvac.snapshot_map_as_floorplan) can show every vacuum on it without any manual "seat" configuration per robot:

Attribute Meaning
home_frame {id, cell_mm, scale, width_px, height_px} — the shared raster this vacuum currently belongs to, or null if it has none yet (just restarted, or its map failed the decoder self-test)
registration {status, method, rotation_deg, score, iou} — status is reference (this vacuum's map founded/still founds the frame), aligned (successfully registered onto it), or unaligned (its map doesn't currently match anything, e.g. a different floor — it gets its own frame automatically)
vacuum_position_home_px / charger_home_px the same points as vacuum_position_px/charger_px, in the shared frame's px space. vacuum_position_home_px.a (heading) is its OWN self-consistent convention — standard image-px atan2 (0°=+x/right, 90°=+y/down), integration ≥ 1.8.1 — distinct from the legacy vacuum_position_px.a contract, where a consumer negates sin to undo a flip baked into that contract's solved affine
path_dry_home_px / path_wet_home_px the same segmented trajectories, in the shared frame's px space
rooms[].bbox_home_px room bounding box in the shared frame's px space
rooms[].outline_home_px the room's actual traced shape (a simplified polygon, ≤ 60 points) instead of just a bounding box
rooms[].home_room_id a stable id shared by two vacuums' rooms once their floor masks overlap enough to be the same physical room — use this (not the room name) to match rooms across vacuums

Every *_home_px field and home_room_id are null/empty for a vacuum with no home-frame registration (home_frame: null) — nothing here changes how a single-vacuum setup behaves.

goto, zone_clean, snapshot_map_as_floorplan and export_map_guide (below) all accept this shared frame as an alternative to their normal per-vacuum input (frame: "home", Fáze 2 of docs/40), and clean/plan transparently pair up the SAME physical room across two robots that each call it something different, once a home frame links them. anyvac-card ≥ 1.7.0 (Fáze 3 core of docs/40) reads these attributes directly in merged mode: a home-frame-registered vacuum renders through one shared identity crop instead of a per-vacuum seat, room rectangles/outlines come from bbox_home_px/outline_home_px, and Pin & Go / Zone send frame: "home" automatically — set up once via the editor's "Snapshot home frame as floorplan" button. A vacuum without a current registration (or an older card) keeps working exactly as before, per-vacuum.

For a floorplan of your OWN (a photo or drawing, not a home-frame snapshot), anyvac-card ≥ 1.8.0 offers a second calibration path (docs/40 §5.B): the editor's "Calibrate floorplan against home frame" button walks through clicking a handful of matching points once against a scratch home-frame snapshot (each click snapped to the nearest wall corner via anyvac.snap_wall_corner above) and once against the floorplan photo, and saves the raw clicked pairs as image_base.home_anchors — never a solved seat. The card re-fits those pairs live every render against the frame's current size, so it keeps working as the frame's canvas grows (the robots exploring further) without asking you to re-click anything.

For that same floorplan-of-your-own case, anyvac-card ≥ 1.9.0 also offers a third, zero-click path (docs/40 §5.A.2, "fiducial markers") when clicking through calibration isn't convenient — e.g. the file also needs rotating, not just cropping. The editor's "Snapshot home frame with markers" button saves a home-frame snapshot with 4 invisible markers baked into its own border (anyvac.snapshot_map_as_floorplan, fiducials: true); crop/resize/ rotate that file in an external editor as needed, keeping it as PNG and without flattening it; then "Detect markers in edited file" calls anyvac.detect_floorplan_fiducials to resolve the exact result and write the SAME image_base.home_anchors shape the manual flow above produces — one less thing for the rest of the card to know about. This only works if the file's alpha channel survives the edit; a flattened image or one re-exported as JPEG loses the markers, which is exactly why it's opt-in and positioned as a last resort in the editor.

Legacy millimetre attributes

The small mm-space fields — vacuum_position, charger, calibration_points and rooms[].x0/y0/x1/y1 — are always published, for custom automations that want to do their own mm math.

The mm path arrays (path, mop_path, path_dry, path_wet) are off by default since 1.1.0. The card has not read them since it moved to the pixel-space contract, and they were measured at roughly 224 KB per vacuum on every 30-second update — pushed over the websocket to every open browser tab whether anything consumed them or not. If you have automations or templates that read them, turn them back on under Settings → Devices & Services → AnyVac → Configure; the integration reloads and starts publishing them again.

path_points / mop_path_points (raw point counts) are published either way.

Recorder

Nothing to configure — the large map attributes (path, rooms, calibration_points, …) are marked as unrecorded by the integration, so they stay out of your recorder database automatically. No recorder: exclude in configuration.yaml is needed.

Notifications

AnyVac never writes notification text itself — it exposes data + events, and you write the message (in any language) when you create an automation. Building blocks:

Per-room timestamp sensors (on the AnyVac Rooms device): sensor.<room>_last_dry and sensor.<room>_last_wet (device_class: timestamp), keyed by room name across all vacuums. Use them for "overdue" logic (now() - states(sensor) > N days).

Events:

Event Data
anyvac_clean_started { vacuum, duid, clean_type } — once per RUN
anyvac_clean_finished { vacuum, duid, clean_type, rooms, duration_min } — once per OUTING
anyvac_run_finished the same payload, plus calibrated (per room and kind: before/after estimate) — once per RUN, when the whole job is done and its calibration/coverage has been written
anyvac_room_done { vacuum, duid, room, reason } — fired when a vacuum has truly left a room it was cleaning (reason: "left", debounced over 2 polls) or on return-to-dock (reason: "docked"). The orchestrator's per-room "wet follows dry" signal.

Run vs outing (docs/36): a job dispatched progressively sends the robot out several times, with a dock trip between batches. anyvac_clean_finished fires on every one of those outings — the orchestrator listens on it to dispatch the next batch — while anyvac_clean_started / anyvac_run_finished bracket the whole job. For a "cleaning done" notification, use anyvac_run_finished: clean_finished would fire once per batch.

All events are fired server-side on the vacuum's cleaning transitions, so notifications built on them fire reliably whether or not the AnyVac card (or any dashboard) is open. rooms is the set of rooms actually visited during the run; duration_min is the measured run length in minutes.

Errors: use the existing Roborock sensor.<vacuum>_vacuum_error.

Auto-installed blueprints. On first setup AnyVac copies three automation blueprints into config/blueprints/automation/anyvac/:

  • AnyVac — Room overdue — pick a room timestamp sensor + threshold days + notify service + message.
  • AnyVac — Clean finished — fires on anyvac_clean_finished; message can use {{ vacuum }}, {{ clean_type }}, {{ rooms }}.
  • AnyVac — Vacuum error — pick the error sensor + notify service + message ({{ error }}).

Create an automation from one (Settings → Automations → Blueprints), choose your notify service and write your own message. Existing (edited) blueprints are never overwritten.

Services (orchestration)

The integration itself plans and runs cleans server-side (so a job survives the dashboard closing) — the AnyVac card sends an intent, not a pre-built plan:

Service What it does
anyvac.clean Clean intent: rooms + mode (dry/wet/both) + optional vacuums restriction, per-room pin, and settings. The integration works out capability, room assignment (LPT-balanced), dry→wet gating and per-room pinning, then executes the resulting task list server-side — a wet-capable robot with 2+ rooms dispatches progressively as rooms become ready instead of waiting for all of them (docs/23). Once a home frame links two robots (above), a room name still resolves to whichever robot actually owns that physical room even if that robot calls it something else — no need to repeat the same clean call once per robot's own name for it.
anyvac.plan Same planner as anyvac.clean, response-only — a preview of the assignment and estimated timeline without starting anything.
anyvac.goto Pin & go: x_pct/y_pct (percent of the rendered map image) → the integration converts to real coordinates and sends the robot. With frame: "home", x_home_px/y_home_px (pixels in the shared home frame) instead.
anyvac.zone_clean Zone clean: two corners as percent of the map image, same conversion (or frame: "home" + *_home_px corners).
anyvac.snapshot_map_as_floorplan Saves a map image entity's picture as the shared floorplan file. With frame: "home", renders a composite of every vacuum registered into the shared home frame instead, straight from their aligned floor/wall masks — no single vacuum's image_entity involved.
anyvac.export_map_guide Draws room-boundary/dry/wet tracing-aid layers for one vacuum's map. With frame: "home", draws every vacuum's rooms and paths on one canvas, with each room's real traced outline (outline_home_px) instead of one vacuum's bounding box.
anyvac.snap_wall_corner Snaps a home-frame pixel point to the nearest wall corner detected from that frame's own wall mask — response-only, {frame_id?, x_home_px, y_home_px} in, {frame_id, snapped, x_home_px, y_home_px, distance_px?} back (snapped: false echoes the input unchanged when the frame has no wall data yet). Used by the card's editor while calibrating a foreign-origin floorplan against the home frame (docs/40 §5.B) to remove most click noise — not something you'd normally call directly.
anyvac.detect_floorplan_fiducials Scans a floorplan file for the invisible fiducial markers snapshot_map_as_floorplan embedded (fiducials: true) and returns home_anchors calibration pairs with zero clicking (docs/40 §5.A.2) — response-only, {path, fiducials} in, {home_anchors, found, missing, image_width, image_height} back. Only works if the file kept its alpha channel through whatever crop/resize/rotate you did to it externally; not something you'd normally call directly — the editor's "Detect markers in edited file" button does.
anyvac.cancel Stops the running job and (by default) returns started robots to base.
anyvac.select_rooms / anyvac.pin_room / anyvac.set_layers / anyvac.set_room_sequence / anyvac.reset_learning UI/learning state — room selection, per-room robot pinning, dry/wet layer visibility, the Roborock app's room order (used for ETA), and clearing bad learned estimates.
anyvac.set_floorplan_seat Save or clear a manual Visual editor floorplan override (docs/41 §4.6 + docs/42) — vacuum with map/appearance/rooms for one vacuum's seat geometry, style and/or per-room overrides (map/appearance each independently nullable; rooms merges per room_key instead, null on one room_key clearing just that room), or vacuum omitted for the card-level overrides instead (image_base the floorplan itself, rooms the shared merged-mode room list, room_style the global room border widths — each independently nullable); null clears the given part. Shared across every dashboard/browser showing that floorplan.

A task started by anyvac.clean runs once its gating conditions (anyvac_room_done per room, or anyvac_clean_finished per vacuum) are met, so a wet robot follows a dry robot per room without colliding — and a robot never receives a new command while mid-clean (that would discard the one in progress). anyvac.run_job (raw task lists) also exists but is an internal implementation detail the card no longer builds plans for directly — use anyvac.clean.

Status

Experimental. See CHANGELOG.md for version history — the integration's version syncs to the anyvac-card version it was released/tested against (so the number tells you the minimum compatible card version). AnyVac reads the Roborock integration's internal runtime data; if a future Roborock release changes that structure, AnyVac degrades gracefully (no data) rather than breaking — please open an issue if that happens.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages