Skip to main content

Jason Slade

You are in: writing/notebook/2026-11-08

·

My Robot Vacuum Drew the Floor Plan for My Home Assistant Dashboard

Pixel-art map of the house at night

I wanted to look at one screen and know which lights were on, what the garage lab was doing, and whether it was raining on the duck coop. My Home Assistant install has about 1,900 active entities across 441 devices and 14 areas. I could get at any of it from a list, but I couldn’t see the house.

There’s a corner of the HA community that builds dashboards that look like a top-down Pokémon town: a pixel-art house where the lamps actually glow when you turn them on. That was the target, a live view of the house as a little game map.

Pixel-art top-down map of the house at night, with the kitchen and dining-room lamps glowing warm yellow and the other rooms dark
Night mode. The kitchen and dining-room lamps are on and glow in their real color and brightness. The small pills are live sensor readouts: aquarium pH, the cats’ litter-box visits, the garage thermostat.

The floor plan came from the vacuum

A dashboard like this is only as good as its floor plan, and I didn’t have one. Home Assistant knew the house had a kitchen, an office and a hall, but not where they were: no floors defined, no coordinates. The first version of the map was a plausible layout invented from the room names. It looked fine. It was wrong.

The fix had been sitting in the dining room the whole time. My Roborock drives lidar around every room each time it cleans, and its map is the most accurate floor plan of the house that exists. The Roborock integration exposes that map as an image entity.

The Roborock map: each room a different solid color, with the vacuum's light-blue cleaning paths drawn over them and the dock marked as a circle on the right
The Roborock’s map as Home Assistant serves it. Each color is one room segment. The blue squiggles are the vacuum’s cleaning paths, and the circle on the right is the dock.

The hard part is tying the vacuum’s room names to its pixels. The map is a flat picture with no labels on the regions. The trick is that the integration draws it with a fixed color palette keyed by segment number: segment 1 is always the same peach, segment 7 always that green, segment 9 always that yellow. That table lives in the map-parser library the integration uses (vacuum-map-parser-base), so it could change in a future version, but today “Dining room” → segment 9 → yellow → this region of pixels is a lookup.

If you want to try it, here’s the recipe:

  1. Save the map image from the vacuum’s image entity.
  2. Call the roborock.get_maps action to get each segment’s id and name.
  3. Look up each id’s color in the palette and collect the pixels of that color.
  4. Assign the leftover pixels (walls, carpet, cleaning paths) to the nearest segment, so each room becomes a full floor footprint instead of a patchy fill.
  5. Set the scale from one wall you can measure. Here the house’s known depth works out to 80 pixels per metre.
  6. Sanity-check the result. The traced rooms total about 1,320 sq ft of vacuumed floor against 1,450 sq ft of living area, and the shortfall is closets and space under furniture.

To check the mapping, the vacuum reported it was docked in the Dining Room, and the dock icon sits inside the yellow region. Nine rooms went from the lidar bitmap into the dashboard’s model this way, carrying the names from the Roborock app.

The lidar map on the left and the same rooms in the pixel-art dashboard on the right
Left: what the vacuum measured. Right: the same rooms in the dashboard. The walls are straightened and the house is enlarged so it reads at a glance, but the room footprints come from the scan.

Three gotchas worth passing on:

  • Renaming rooms in the Roborock app doesn’t reach HA right away. Until I renamed them, most of the vacuum’s rooms were just called “Bedroom”, and that’s where the first wrong layout got its names. After the rename, HA kept serving the old map and old names for hours, and every normal refresh returned the stale copy, including the roborock.get_maps action and homeassistant.update_entity. In my case nothing short of reloading the Roborock config entry pulled the new map.
  • Renaming doesn’t move walls. Diffing the before and after maps showed only the dock icon and cleaning paths had changed. The geometry was right all along; the earlier model had just attached the names to the wrong rooms.
  • Check the orientation with your own eyes. One version was traced perfectly and drawn mirrored top to bottom. Every wall was right relative to every other wall, and the whole house was upside down. I spotted it as soon as I looked, and the automated checks didn’t catch it.

How it’s built: pixel art as a skin, lighting drawn live

The usual community recipe is to paint the floor plan in a tile editor, then hand-cut a “lit” overlay image for every single light and fade each one in when the light turns on. It looks great, but I have 31 lights and 152 entities I wanted on the map, and every new lamp would mean another overlay. So I did it differently:

house-model.json bake script one pixel-art PNG renderer live HA states what you see rooms · furniture · 152 entities run once per layout the daytime art plain JS + SVG every state change glows · motion · doors · weather
One model file feeds two things: a script that bakes the pixel art once, and a renderer that draws everything live on top of it.
  • One model file describes the rooms, the furniture and where each entity sits.
  • A bake script (run once, like baking lighting in a game engine) turns that model into a single pixel-art image. If I move a wall, I re-run it.
  • A renderer draws everything live on top. At night it dims the base image, and each lit lamp adds a radial glow tinted by the bulb’s actual color and scaled by its brightness. Motion sensors pulse, doors swing, and the weather entity drives rain, snow or fog over the yard. Day and night follow the real sun.
  • Adding a device is one line in the model, with no new art.

The renderer doesn’t know anything about Home Assistant. It takes a model and a list of states. A small wrapper makes it a Lovelace custom card, and a different wrapper can feed it a snapshot file for a web page.

For the art I didn’t use actual Pokémon tiles. Those are Nintendo’s and not licensed for this. It’s LimeZu’s Modern Interiors and Modern Farm packs, which are cheap, licensed for commercial use, and very much in the same spirit. The cars, the Pioreactor (a small open-source bioreactor) and the aquarium tanks aren’t in either pack, so the bake script draws them pixel by pixel, and it shows.

Pixel-art top-down map of the house in daytime, with wood and tile floors, furniture, and small sensor readouts in each room
Day mode. The glows mostly wash out and the pixel art carries it.

The dashboard that only broke in a background tab

If a restored HA tab comes up blank, click on it.

During testing the dashboard sometimes loaded completely blank, with no errors. All twelve Lovelace resources were swapped for empty stubs and it was still blank. Home Assistant builds the dashboard view inside a requestAnimationFrame callback (the browser’s “run this before the next frame” hook), and browsers pause those in hidden tabs. The testing ran in an automated browser that kept the tab in the background, so the view never got built. In a normal, visible tab it works fine. That’s standard browser behavior.

Where it stands

It’s live in Home Assistant, with all 152 entities on the map and all 31 light glows checked to land inside their own rooms on the pixel art. Rough edges I know about:

  • Furniture is placed by footprint, not by eye. The beds look small, and the kitchen floor is a very loud checkerboard.
  • HA caches /local/ files for 31 days, so re-baking the image under the same URL leaves old art under new walls. Give the image a new URL on every re-bake, for example by setting pixel_base: /local/floorplan-twin/pixel-base.png?v=4 in the card config. I haven’t applied that fix on my own dashboard yet.

The public version of the map isn’t built yet, and privacy is the reason it needs care. A live map that shows motion, open doors and who’s home also tells anyone reading it when the house is empty. So when it goes up here, it’ll be push-only from the house, with lights, temperatures and lab gauges live and doors, motion, cameras and presence left out. The pictures in this post are rendered the same way.

Meanwhile the vacuum keeps mapping. If I ever move a wall, it’ll know before Home Assistant does.

Tiles: LimeZu (limezu.itch.io).

← All posts

→ Subscribe by RSS