TL;DR: The usual LD2450 X, Y, and speed entities are fine for occupancy and
dashboards, but they are a poor substrate for trajectory math: they arrive as
separate Home Assistant state changes, use receive timing, and may deduplicate
unchanged values. I now publish all three target slots as one coherent state with
an ESP device timestamp:
t_ms|x1,y1,v1|x2,y2,v2|x3,y3,v3
The clean-room ESPHome package, parser, optional Pyscript CSV logger, BLE-presence
add-on, tests, and build examples are here:
Why one atomic frame?
Trajectory calculations care about small timing and coordinate errors. If X from
one radar frame is combined with Y from the next, or Home Assistant receive time
is used as the sample clock, velocity and heading estimates can move noticeably.
The package uses ESPHome’s native ld2450.on_data callback, after a complete
periodic frame has been parsed. It publishes raw millimetres, raw speed quanta,
all three target slots, and millis() from the ESP in one text state. Because
the timestamp changes every frame, repeated dwell coordinates are not silently
lost as duplicate states.
This does not make the LD2450 itself more accurate. It also does not solve
multipath ghosts, unstable target-slot identity, association across gaps, or
origin/destination classification. It provides a cleaner input stream on which
those later stages can operate.
The timestamp is device uptime, not wall-clock time. It wraps after about 49.7
days and resets on reboot; the included parser handles rollover and treats a
probable reboot as a discontinuity.
SCREEK 2A context
I developed and physically exercised these changes as modifications to my
private customized SCREEK Human Sensor 2A firmware. The public repository does
not contain SCREEK source, YAML, or binaries. It independently demonstrates the
same hardware capabilities using ESPHome’s public native components because the
SCREEK source repository currently has no declared redistribution license.
The public examples compile with ESPHome 2026.6.1, but have not yet been flashed
as complete replacement firmware. They are not official SCREEK updates, patches
to apply over a vendor image, or drop-in replacements for all vendor features.
Keep a recovery path before flashing experimental firmware.
Optional BLE presence
The repository also includes an optional passive iBeacon presence package for
the SCREEK 2A’s ESP32-C3 radio. BLE presence is a coarse, seconds-scale signal,
so Home Assistant arrival time is sufficient there; it does not need the radar
stream’s device-time precision.
The example listens for 100 ms in every 200 ms interval, so advertisements can
be missed by design. The ESP32-C3 also time-shares one radio between BLE and
Wi-Fi. Repeated advertisements plus a presence timeout tolerate occasional
misses, but this is not a delivery guarantee or an access-control signal.
The ESP32 scanner and the LD2450 module’s own Bluetooth radio are separate. The
radar radio is not needed for beacon scanning and should be disabled after setup
when unused. That security finding and procedure are covered here:
Recorder and privacy
An atomic frame can update several times per second. Exclude the text sensor from
Recorder when a live consumer is sufficient, or use the included buffered CSV
logger deliberately. Radar captures reveal movement patterns; beacon identifiers
and presence history are also tracking data. Keep real captures, geometry, and
identifiers private.
This is part of a series
This post is the transport/data-foundation part of a longer set of LD2450
writeups. Dates are TBD, and the order may change as the reusable code is tested.
-
LD2450 Bluetooth hardening - published. It separates the ESP32 and radar
radios, shows how the open radar configuration interface can affect dependent
automations, and covers disablement and read-back. -
Atomic radar frames - this post. Coherent target slots, device timing,
parsing, logging, rollover handling, and the clean-room SCREEK 2A demo. -
Radar lab setup and tuning - planned. A privacy-safe replay/visualization
lab with synthetic fixtures, placement checks, native region/filter tuning,
multipath diagnosis, and a repeatable way to compare parameter changes. -
Trajectory handling on lightweight Home Assistant hardware - planned.
This will include two paths:- a practical starting path with documented constant-velocity Kalman defaults,
gap coasting, outlier/association gates, and manually drawn
origin/destination portals - a lab path for replaying captures, inspecting residuals and track splits,
tuning noise/gates, and measuring CPU/memory cost
The defaults will be starting points with explicit units and cadence
assumptions, not universal magic values. Target slots are not person IDs, so
cross-slot association must come before filtering/classification. - a practical starting path with documented constant-velocity Kalman defaults,
-
Publishing from the lab to Home Assistant - planned. A neutral, versioned
rule/config export; a lightweight HA/Pyscript adapter; reload and rollback;
binary/event sensors with confidence and reason fields; and observability when
input cadence or a classifier becomes unhealthy.
Rich mobile notifications fit in that fifth part rather than in the core tracker:
once HA has a stable event contract, notifications can add trajectory summaries,
confidence/reason text, cross-sensor corroboration, grouping, cooldowns, and an
action for marking a false alert without coupling notification UX to the tracking
algorithm.
A possible later evaluation writeup would cover ground-truth annotations,
synthetic regression cases, confusion matrices, and optional corroboration from
door contacts, PIR, or coarse BLE presence. Those sensors should validate or
identify a passage; they should not be hidden prerequisites for the basic LD2450
pipeline.
Feedback on the frame contract and on what would make the quick-start trajectory
path useful on modest HA hardware is welcome.