SmartestHome/render
Amir Alexander Abdelbaki 483cbce728 Exact positions inside a room, fused from radar and BLE
The 3D floorplan could say which room somebody was in. This makes it able to
say where in the room — from a different sensor, kept deliberately separate
from the one that knows who they are.

Room-level presence comes from BLE, which cannot give coordinates: RSSI-to-
distance is noisy enough that trilateration in a house lands in the wrong
room. Coordinates come from mmWave radar (LD2450-class), which tracks moving
targets and reports x/y — and cannot say who anybody is, because it sees a
moving blob.

So the two are fused, by a rule that refuses far more often than it commits:

    exactly one occupant in the room AND exactly one target in the room
        -> that target is that person
    anything else
        -> targets stay anonymous, occupants stay unpositioned

Two people in a room are two blobs that cannot be told apart. Guessing which
is which would put a name on the wrong person, and a display that does that
occasionally is worse than one that never tries — its wrong answers are
indistinguishable from its right ones. position_ambiguous says so out loud.

Which room a target is in is computed from the polygon rather than from which
sensor saw it: a radar in an open-plan kitchen sees into the living room, and
attributing by sensor would put people through walls.

identity
  floorplan_levels gains metres_wide; without it positions are not computed
  and the API reports that rather than guessing a scale. New floorplan_sensors
  table holds where each radar sits on the plan and which way it faces —
  drawn by a human, because a wrong rotation mirrors every target it reports
  and the result looks plausible rather than broken. Targets at exactly (0,0)
  are dropped: that is how these radars say "nothing here", and treating it
  as a detection grows a phantom person on top of every sensor.

render/floorplan-3d
  A fused person is drawn at their coordinate with a footprint dot, since a
  marker floated above the floor otherwise reads as further back in the room.
  An unattributed target is a hollow dashed puck with a question mark — no
  colour, no initial, because every visual language here for a person is
  reserved for people the system can name. A radar target lights the room even
  with nobody named: somebody is in there, and that the house cannot say who
  is a fact about the house.

Hardware: HLK-LD2450 added to components.md, ~EUR 15-25 per room, with the
advice to buy one and check its facing before buying more.

Position maths and the fusion rule are unit-tested headlessly. No radar has
been bought, mounted or read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FanS1vyE2gLhGkqKq6HtYj
2026-08-10 15:03:45 +02:00
..
floorplan-3d Exact positions inside a room, fused from radar and BLE 2026-08-10 15:03:45 +02:00
media-visualiser Workshop assistant, fleet monitoring, infra health, and shared endpoint surfaces 2026-08-10 14:54:37 +02:00
README.md Exact positions inside a room, fused from radar and BLE 2026-08-10 15:03:45 +02:00

README.md

render/ — shared frontend surfaces

Two pages that every endpoint can show, kept here rather than inside one component because they belong to no single service: the now-playing screen reads Home Assistant, the floorplan reads identity, and both are shown on thin clients, touch panels, the kitchen and door panels alike.

Same rules as digest-canvas-sdk and canvas-sdk: vendored, dependency-free, no build step, config from ?query=params so nothing here holds a secret. Copy the directory into a host's image; do not serve it from one place, or a kiosk that cannot reach the container host shows nothing.

What Reads
media-visualiser/ now-playing: circular spectrum, album-art colours, lyrics HA media_player state
floorplan-3d/ who is in which room, extruded identity GET /floorplan/presence

media-visualiser

nowplaying.html?ha=http://ha:8123&token=<HA token>&entity=media_player.living_room

Two tiers, and the distinction is load-bearing. CAVA reads an audio stream; most endpoints do not have one — a kitchen panel showing what the living room is playing has no audio to analyse and never will. So:

  • reactive — audio is local. Real FFT via WebAudio (attachAudio(el)), or levels pushed from cava -r by the host's agent (pushLevels([...])).
  • synthetic — everywhere else. The ring breathes from track position and tempo.

The synthetic tier says so on screen ("visual rhythm — not an audio analysis") and sets data-tier on the canvas. That matters: the moment somebody believes it is a spectrum, every bass drop it misses becomes a bug report.

Palette extraction rejects near-greys and near-blacks before ranking (album art is full of both, and a naive palette off a dark cover is four indistinguishable greys), then lifts each colour until it clears the background — the step that gets skipped, and why so many art-coloured visualisers are invisible on dark covers.

Lyrics: synced LRC highlights and scrolls; plain text is shown but never auto-scrolled at a guessed rate, which is wrong within ten seconds and stays wrong. Most tracks have none, so the layout treats absent as the normal case.

floorplan-3d

floorplan.html?api=http://identity:8097&token=<identity token>[&level=<id>]

Canvas 2D, not three.js — a change from what docs/endpoint-surfaces.md planned, made while building it. The scene is prisms on a plane: no lighting model, no textures, no model import. An isometric projection with painter's-algorithm sorting draws exactly that in ~200 lines, runs predictably on the small panels (where a WebGL context is much less predictable), and keeps the frontend at zero dependencies. If this ever needs real lighting or an imported model, three.js becomes right and this becomes the fallback.

Inherited from the Pebble watchapp deliberately, because they render the same payload and divergence would mean one of them is lying:

  • Occupied rooms lit, empty rooms dark — carried in lightness, not hue, because colour already means who.
  • A third state, dashed, for rooms HA never reports on. Drawing "no data" as "empty" is a quiet lie.
  • Occupants as colour + initial, upgraded to their photo where there is room, with the colour ring kept either way — it is what ties this marker to the same person on the watch and in the admin panel.
  • unplaced people get a visible shelf. They are who you are most often looking for.

Exact positions, when the level has a metre scale and mmWave sensors placed on it: a person fused to a single radar target is drawn at that coordinate with a footprint dot on the floor (without it, a marker floated above the floor reads as further back in the room). An unattributed target is a hollow dashed puck with a question mark — no colour, no initial, because every visual language here for a person is reserved for people the system can name. A radar target lights the room even with nobody named: somebody is in there, and that the house can't say who is a fact about the house.

Drag to orbit; pitch is clamped and there is no free-fly, because an unconstrained camera on a wall panel is something people knock askew and cannot get back.

The caveat no rendering fixes: this is only as right as room-level presence, which has never been measured in this house. A beautiful 3D house with everybody on the unplaced shelf is a beautiful picture of nothing — test that first with the admin panel's floorplan tab, Live ticked.