Skip to content

feat(vl53l): VL53L5CX multizone ToF driver (espp::Vl53l5cx) - #846

Merged
finger563 merged 16 commits into
mainfrom
feat/vl53l5cx
Oct 19, 2026
Merged

finger563 merged 16 commits into
mainfrom
feat/vl53l5cx

Conversation

@finger563

@finger563 finger563 commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds espp::Vl53l5cx, a driver for the VL53L5CX 4x4 / 8x8 multizone time-of-flight sensor, to the vl53l component next to the existing VL53L4CD/CX driver (both talk I2C with 16-bit register addresses and share BasePeripheral plus the component scaffolding).

  • The sensor has no firmware in ROM: initialize() (run by the constructor by default) reboots it and uploads ST's ~84 KB firmware, then configures it through the DCI command interface. About two seconds at 400 kHz; the sensor supports 1 MHz.
  • The logic is a port of ST's Ultra Lite Driver 1.1.2 (BSD-3-Clause, as redistributed by stm32duino/VL53L5CX); the firmware / default configuration / crosstalk blobs are generated from its vl53l5cx_buffers.h and keep the license in their file headers. The component README has a license note.
  • API: resolution, ranging frequency (validated against the resolution), integration time, sharpener, target order, ranging mode, power mode, I2C address change; start_ranging() / stop_ranging(), is_data_ready() polling, get_ranging_data() into a RangingData (distance, sigma, signal / ambient rates, SPAD count, reflectance, target count and target status per zone, row-major); the motion indicator plugin.
  • One target per zone (the ULD default). The crosstalk calibration and detection threshold plugins are documented follow-ups.
  • components/vl53l/vl53l5cx_example (Kconfig: pins, I2C clock, resolution, frequency) prints the distance grid; docs pages, Doxyfile and the CI build matrix are updated.

Notes for review: BasePeripheral::write_many_to_register stages each write on the stack, so the driver chunks writes to 512 bytes; the I2C device timeout has to cover those and the ~1.4 KB frame reads (the example uses 100 ms).

WebUSB console (second commit)

A separate example next to the first one, so the sensor can be watched and driven from a browser over the dispatcher / stream_frame protocol:

  • Protocol vl53l5cx_protocol.hpp (module id 10, protocol espp.vl53l5cx v1, standard-library only, host test components/vl53l/test/vl53l5cx_protocol_host_test.cpp): every setting (resolution, frequency, integration time, sharpener, target order, ranging mode, power mode, motion indicator window, I2C address), start / stop / initialize, a per-transport frame stream (every Nth frame), GET_FRAME, and a STATUS snapshot (settings read back from the sensor when idle, cached while ranging).
  • Service espp::Vl53l5cxService (vl53l5cx_service.hpp): the application owns the one poll loop and hands each frame to every instance through on_frame(), so the vendor (WebUSB) and CDC (Web Serial) transports share one sensor and one mutex.
  • Example components/vl53l/vl53l5cx_webapp_example (ESP32-S3 / ESP32-P4; Kconfig: pins, part, boot resolution / frequency, ranging at boot, P4 USB port): the sensor service plus the standard System / Monitor / OTA / CoreDump services on both transports, OTA + coredump partition layout, like the MCP266 webapp example.
  • Web app components/vl53l/web/vl53l5cx_console.html: live 3D view (zones at their measured position in the field of view as a surface, or a bar height field), coloured by distance / signal / ambient / sigma / reflectance / status, numeric grid, per-zone details, motion indicator, all settings, start / stop / initialize, CSV export. Shares the byte-identical discovery / connection / navigation / hand-back blocks with the other consoles (the node test list is extended). New apps-index category sensors.
  • Docs: web apps page, module id table, distance sensor pages; Doxyfile; READMEs; CI entry (esp32s3, manager off).
  • espp::UsbDevice fix (third commit): on the ESP32-P4 the full-speed port (Config::port 0) enumerated on GPIO 26 / 27, because IDF leaves USB-OTG 1.1 on the second internal FS PHY while the dev kits' USB-C (and the USB-Serial-JTAG console) is on GPIO 24 / 25. New Config::fs_phy (default 0) selects the PHY pads; the example defaults the P4 dev kits to the full-speed port.

Test plan

  • components/vl53l/vl53l5cx_example builds for esp32s3 (IDF 6.1), no warnings
  • components/vl53l/example (VL53L4) still builds
  • cppcheck with the CI flags is clean on the new sources
  • Hardware: VL53L5CX breakout on an ESP32-P4-WIFI6 dev kit (init 2.1 s, 8x8 @ 15 Hz, 4x4 @ ~56 Hz, motion indicator, recovery after a host reset mid-frame)
  • vl53l5cx_webapp_example builds for esp32s3 and esp32p4 (IDF 6.1, manager off), boots on the P4 kit (sensor ranging, TinyUSB on the high-speed port)
  • node components/dispatcher/web/test/resolve_module_id_test.js and vl53l5cx_protocol_host_test pass; doc/generate_apps_index.py accepts the new page
  • Web app rendered in Chrome with a synthetic 8x8 frame (surface + bars views, grid, zone details, motion panel)
  • Protocol against the device over its CDC (Web Serial) interface on the P4 kit: discovery, every request incl. error paths, 4x4 @ 30 Hz streaming with no sequence gaps, 8x8 @ 15 Hz with divider 3 + motion indicator, snapshot, re-initialize, live STATUS while ranging
  • Web app connected through the browser chooser (WebUSB and Web Serial) on the P4 kit; screenshots / recording in the comment below
  • VL53L7CX firmware image (Config::variant): bundled from the same ULD source, not tested on hardware (no part available)

🤖 Generated with Claude Code

https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU

Adds a driver for the VL53L5CX 4x4 / 8x8 multizone time-of-flight sensor to
the vl53l component, next to the VL53L4CD/CX driver (both use 16-bit register
addresses over I2C; they share BasePeripheral and the component scaffolding).

The sensor has no firmware in ROM, so the driver reboots it and uploads ST's
~84 KB firmware at initialize(), then talks to it through the DCI command
interface. The logic is a port of ST's Ultra Lite Driver 1.1.2 (BSD-3-Clause,
as redistributed by stm32duino/VL53L5CX; the firmware / default configuration
blobs are generated from its vl53l5cx_buffers.h, license kept in the file
headers) to espp conventions: error codes, bool returns, a recursive mutex
around the multi-step transactions, chunked writes (BasePeripheral stages a
write on the stack).

- resolution, ranging frequency (validated against the resolution),
  integration time, sharpener, target order, ranging mode, power mode, I2C
  address change; start / stop ranging, is_data_ready() polling and
  get_ranging_data() into a RangingData (distance, sigma, signal / ambient
  rates, SPAD count, reflectance, target count, target status per zone)
- motion indicator plugin ported (with the ULD's set_resolution write bug
  fixed); one target per zone; xtalk calibration and detection thresholds are
  documented follow-ups
- vl53l5cx_example (Kconfig: pins, I2C clock, resolution, frequency) prints
  the distance grid; docs page + example page, Doxyfile, CI build entry,
  component README / manifest updated

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU
Copilot AI balanced review requested due to automatic review settings October 14, 2026 18:03
@github-actions

Copy link
Copy Markdown

✅Static analysis result - no issues found! ✅

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

5 open findings
What changed in this PR

Adds a new espp::Vl53l5cx multizone ToF driver (VL53L5CX) alongside the existing VL53L4 driver, including an ESP-IDF example and documentation/CI wiring.

Changes:

  • Introduces components/vl53l/include/vl53l5cx.hpp + src/vl53l5cx.cpp implementing firmware upload, DCI configuration, ranging, and motion indicator support.
  • Adds a dedicated vl53l5cx_example project (Kconfig + main) and hooks it into Doxygen and CI builds.
  • Adds Sphinx docs pages for the VL53L5CX driver and example; updates component README and manifest metadata.
File Description
doc/​en/​distance_sensors/​vl53l5cx_example.md Exposes the example README in the docs tree.
doc/​en/​distance_sensors/​vl53l5cx.rst New VL53L5CX docs landing page + API include.
doc/​en/​distance_sensors/​index.rst Adds VL53L5CX to the distance sensors doc index.
doc/​Doxyfile Includes the new example + header in Doxygen input.
components/​vl53l/​vl53l5cx_example/​** New standalone ESP-IDF example project for VL53L5CX.
components/​vl53l/​src/​vl53l5cx_firmware.hpp Declares bundled firmware/config blob symbols and sizes.
components/​vl53l/​src/​vl53l5cx.cpp Implements the VL53L5CX driver logic (ULD port).
components/​vl53l/​include/​vl53l5cx.hpp Public driver API/types/configuration for VL53L5CX.
components/​vl53l/​idf_component.yml Updates description/tags and registers the new example.
components/​vl53l/​README.md Documents both drivers and adds VL53L5CX license note.
components/​vl53l/​CMakeLists.txt Ensures src/ is compiled into the vl53l component.
.github/​workflows/​build.yml Adds the new example to the CI build matrix.

🧠 Review effort: Lite


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread components/vl53l/src/vl53l5cx.cpp Outdated
Comment thread components/vl53l/src/vl53l5cx.cpp Outdated
Comment thread components/vl53l/src/vl53l5cx.cpp
Comment thread components/vl53l/src/vl53l5cx.cpp
Comment thread components/vl53l/src/vl53l5cx.cpp
- initialize() refuses a device that does not answer with the VL53L5CX ids
  (no_such_device) instead of uploading the firmware into it
- send_offset_data: the NVM header is dropped with a bounded memmove of the
  480 payload bytes (the ULD shifted 4 bytes past the offset buffer)
- poll_for_answer validates size / pos
- comments on the two ULD conventions kept on purpose (output list length
  13, stream count 255 excluded)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unsynchronized status reads, insufficient transfer timeouts and failure-path state handling need correction.

2 open findings
5 resolved since last review
Previously missed (4)

In code that hasn't changed since last review

Medium severity Restore register page after failed ID probe

components/​vl53l/​src/​vl53l5cx.cpp:236

If either ID read fails after selecting page 0, short-circuit evaluation skips the page-2 restore. Calling this health check on an initialized sensor can leave subsequent frame reads and DCI commands accessing the wrong register page after the bus recovers. Always attempt to restore page 2, preserving the original error if the probe failed.

Medium severity Restore register page after failed power-register read

components/​vl53l/​src/​vl53l5cx.cpp:490

If selecting page 0 succeeds but reading the power register fails, this return skips the page-2 restoration below. Subsequent DCI commands and frame reads then access the wrong register page even after the I2C fault clears. Attempt the restore on this error path with a separate error code so the original failure is preserved.

Medium severity Restore register page after xshut-bypass failure

components/​vl53l/​src/​vl53l5cx.cpp:893

If selecting page 0 succeeds but the xshut-bypass write fails, this error path does not restore page 2. Retrying start_ranging() then begins with a DCI resolution read against the wrong page, so a transient I2C failure can prevent recovery. Attempt the page-2 restore before returning, preserving the original error.

Medium severity Preserve ranging state when stop or cleanup fails

components/​vl53l/​src/​vl53l5cx.cpp:931

If a stop-command write fails or the MCU-stop poll times out, stopping has not been confirmed, but this still clears ranging_. Subsequent frame reads are rejected and configuration setters bypass their while-ranging guard even though the sensor may still be active. Preserve the ranging state on failure; only clear it after a successful stop and cleanup.

🧠 Review effort: Balanced

Comment thread components/vl53l/include/vl53l5cx.hpp Outdated
Comment thread components/vl53l/vl53l5cx_example/main/vl53l5cx_example.cpp Outdated
- initialized_ / ranging_ are atomics (is_initialized() / is_ranging() read
  them without the mutex)
- is_alive(), get_power_mode() and start_ranging() restore the firmware's
  register page (0x7fff = 0x02) on their failure paths too, keeping the
  original error, so a transient I2C fault never leaves later DCI / frame
  reads on page 0
- stop_ranging() clears ranging_ only after a confirmed stop and cleanup
- Vl53l5cx::MAX_TRANSFER_BYTES + i2c_timeout_ms(clock_hz): the example sets
  the bus and device timeouts from the clock (the legacy I2C API uses the bus
  timeout; 100 ms did not cover an 8x8 frame at 100 kHz)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU
@finger563

Copy link
Copy Markdown
Contributor Author

The four "previously missed" items are fixed in f646e5d as well: is_alive(), get_power_mode() and start_ranging() restore register page 2 on their failure paths (original error preserved), and stop_ranging() keeps ranging_ set unless the stop and cleanup were confirmed.

@finger563
finger563 requested a balanced review from Copilot October 14, 2026 18:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Address-change callbacks and error-recovery paths have unresolved correctness issues.

0 open findings

2 resolved since last review
Previously missed (3)

In code that hasn't changed since last review

Medium severity Incoming error prevents recovery after successful retry

components/​vl53l/​src/​vl53l5cx.cpp:107

Clear the incoming error before this read. The separate-write/read fallback selected by this driver's Config leaves ec unchanged on success (components/base_peripheral/include/base_peripheral.hpp:333–344). After a transient read failure, retrying is_data_ready() or get_ranging_data() with the same error-code variable therefore still returns false even when both I2C callbacks succeed. Clearing it here allows recovery while retaining any new transfer error.

Medium severity Page restoration skipped when cleanup write fails

components/​vl53l/​src/​vl53l5cx.cpp:950

If a cleanup write to 0x14, 0x15 or 0x09 fails after selecting page 0, this short-circuit chain skips restoring page 2. Subsequent frame reads or a stop retry then access the wrong register page, even after a transient I2C failure clears. Attempt the final page restore independently and preserve the first cleanup error.

Medium severity Address-aware callbacks ignored for configurable I2C addresses

components/​vl53l/​vl53l5cx_example/​main/​vl53l5cx_example.cpp:52

These adapters ignore the callback address and stay bound to 0x29 (components/i2c/include/i2c.hpp:872–892). If a caller uses this setup with set_i2c_address(), the sensor changes address, but the final page-restoration write and later transfers still target 0x29 and fail. Use address-aware bus callbacks here; the bus already has the required timeout. Also document the callback requirement on set_i2c_address() so callers do not use fixed-device adapters for address changes.

🧠 Review effort: Balanced

- write_multi / read_multi clear the incoming error code first: the base
  class's separate write / read path only sets it, so a stale error from an
  earlier failure made a successful retry of is_data_ready() /
  get_ranging_data() look failed
- stop_ranging() restores the firmware's register page even when the cleanup
  writes failed, reporting the first error
- the example uses address-aware bus callbacks (lambdas over I2c::write /
  I2c::read) so set_i2c_address() keeps working; the Config / set_i2c_address
  docs state that requirement

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU
@finger563

Copy link
Copy Markdown
Contributor Author

Round 3's three "previously missed" items are fixed in 2c22f41: write_multi / read_multi clear the incoming error code (the base class's separate write / read path only sets it); stop_ranging() restores register page 2 independently of the cleanup writes and reports the first error; the example uses address-aware bus callbacks (lambdas over I2c::write / I2c::read) and the Config / set_i2c_address() docs state that the callbacks must honour their address argument.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The firmware-driven hardware port still needs device validation and human review, with power-mode reporting and address-change recovery issues unresolved.

0 open findings

Previously missed (2)

In code that hasn't changed since last review

Medium severity Restore page 2 when address write fails

components/​vl53l/​src/​vl53l5cx.cpp:492

If selecting page 0 succeeds but the address write fails before changing the address, this return leaves the sensor on page 0. Later DCI and frame operations assume page 2, so they access the wrong registers even after the bus recovers. Attempt to restore page 2 on this failure path, using a separate error code to preserve the original failure.

Medium severity Accept 0x05 as a valid wakeup power mode

components/​vl53l/​src/​vl53l5cx.cpp:514

start_ranging() writes 0x05 to register 0x09 to enable xshut bypass, but this switch accepts only 0x04 and 0x02. Reading back the awake-with-bypass value therefore sets an I/O error in get_power_mode() during normal ranging. Accept 0x05 as WAKEUP too.

🧠 Review effort: Balanced

- set_i2c_address() restores the firmware's register page on the old address
  when the address write failed (the address did not change)
- get_power_mode() accepts 0x05 (awake with the xshut bypass that
  start_ranging() sets) as WAKEUP instead of reporting an I/O error while
  ranging

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU
@finger563

Copy link
Copy Markdown
Contributor Author

Round 4's two "previously missed" items are fixed in 1951aad: set_i2c_address() restores register page 2 on the old address when the address write fails, and get_power_mode() accepts 0x05 (awake + xshut bypass during ranging) as WAKEUP.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Firmware initialization, ranging and motion-indicator behavior still need hardware validation and final human review.

0 open findings

Previously missed (1)

In code that hasn't changed since last review

Low severity Correct default DCI_INT_TIME from 10 ms to 5 ms

components/​vl53l/​include/​vl53l5cx.hpp:183

The bundled configuration sets DCI_INT_TIME (0x545C) to 10,000 microseconds, and initialize() does not override it. The default integration time is therefore 10 ms, not 5 ms. Please correct this value so callers do not underestimate the integration budget, particularly with four integrations per 8x8 frame.

🧠 Review effort: Balanced

…ation time

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU
@finger563

Copy link
Copy Markdown
Contributor Author

Round 5's item is fixed in a0d9c38: the bundled default configuration sets DCI_INT_TIME to 10 000 µs, so the initialize() doc now says 10 ms (verified against the blob bytes at 0x545C).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The sensor-protocol port needs the planned hardware validation of initialization, ranging, and motion detection, followed by human review.

0 open findings

🧠 Review effort: Balanced

…irmware-error details

The VL53L7CX (90 deg field of view) is the VL53L5CX's silicon, ids, command
interface, default configuration and crosstalk shape with a different
firmware image - the parts cannot be told apart over I2C, and the wrong image
fails at the first firmware command after boot (the NVM read, firmware error
0x9f on the bench). Both images are bundled (BSD-3-Clause, ST's ULD via
stm32duino/VL53L7CX 1.2.3) and Config::variant selects one; the example has
a Kconfig choice. The MCU boot wait is the newer ULD's (0x06 bit 7, then the
boot error code in 0x07, reported), and a firmware error log now carries the
four status bytes and what was expected.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU
…based example

- the first transaction after a host reset failed when the sensor was still
  clocking out the frame the previous firmware was reading (SDA held low):
  the identity probe retries a few times, so a reset while ranging now logs
  one failed write and initializes (verified on an ESP32-P4-WIFI6 dev kit)
- example: Kconfig hardware choice for the ESP32-P4 dev kits (SDA 7 / SCL 8;
  the empty P4 defaults left the pins unset), a plain polling task instead
  of a 5 ms timer (a 1.4 KB frame read takes ~40 ms at 400 kHz and tripped
  the timer's period warning every frame), and a per-frame summary of why
  zones are invalid (target status counts)

Bench, VL53L5CX on an ESP32-P4-WIFI6 dev kit: initialize 2.1 s at 400 kHz,
8x8 at 15 Hz, 96 frames in 6.4 s, scene distances 0.43 .. 2.8 m.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU
@finger563

Copy link
Copy Markdown
Contributor Author

Hardware-tested on an ESP32-P4-WIFI6 dev kit with a VL53L5CX breakout (I2C 400 kHz, SDA 7 / SCL 8): initialize in 2.1 s, 8x8 at 15 Hz (96 frames in 6.4 s), plausible scene distances 0.43 .. 2.8 m; a reset while ranging recovers through the new identity-probe retry. Two fixes came out of it (f9a72f7, and this push): the MCU boot wait is the newer ULD's (the old 0x06 == 0 check could pass before the MCU was up and the first firmware command then failed with error 0x9f), and the first transaction after a host reset is retried. VL53L7CX support (same ids, different firmware) is selected by Config::variant; untested on an L7CX. Remaining test-plan items: 4x4 @ 60 Hz and the motion indicator.

…per frame

Bench on the ESP32-P4-WIFI6 dev kit: 4x4 @ 60 Hz ran 416 frames in 7.4 s
(~56 Hz with the console printing in the loop); 8x8 @ 15 Hz with the motion
indicator on (400..1500 mm window) reported 16 aggregates with low motion
values on a static scene.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU
@finger563

Copy link
Copy Markdown
Contributor Author

Remaining test-plan items done on the same P4 kit: 4x4 @ 60 Hz (416 frames in 7.4 s, ~56 Hz with console printing in the loop) and the motion indicator (new Kconfig option in the example; 16 aggregates, low values on a static scene). Everything in the test plan is now hardware-verified on a VL53L5CX; the VL53L7CX path is untested.

finger563 and others added 5 commits October 19, 2026 14:43
…ple, web app)

Add a dispatcher-module front-end for the multizone ToF driver so a browser
can watch the 4x4 / 8x8 zone distances live in 3D and exercise the sensor API:

- `vl53l5cx_protocol.hpp`: wire protocol (module 10, `espp.vl53l5cx` v1):
  every setting, start / stop / initialize, a per-transport frame stream
  (every Nth frame) and a STATUS snapshot; standard-library only, with a
  host test (`test/vl53l5cx_protocol_host_test.cpp`).
- `vl53l5cx_service.hpp`: `espp::Vl53l5cxService`; the application owns the
  one poll loop and hands each frame to every instance via `on_frame()`, so
  the vendor (WebUSB) and CDC (Web Serial) transports share one sensor.
  Settings are cached while ranging (the firmware cannot be queried then).
- `vl53l5cx_webapp_example`: ESP32-S3 / ESP32-P4 (Kconfig USB port choice),
  with the standard system / monitor / OTA / core-dump services, OTA +
  coredump partition layout, 24 KB main task stack (the driver's buffers and
  the services' frame copies live on it).
- `web/vl53l5cx_console.html`: single-file app with a hand-rolled Canvas 3D
  view (surface at the measured positions in the field of view, or bars),
  numeric grid, zone details, motion indicator, settings panel, CSV export;
  shares the byte-identical discovery / connection / navigation blocks with
  the other consoles (test list extended). New apps-index category "sensors".
- Driver: `variant()` / `is_motion_indicator_enabled()` getters.
- Docs (web_apps, module id table, distance_sensors), Doxyfile, READMEs,
  component manifest / CMake deps, CI build entry.

Verified: builds for esp32s3 and esp32p4 (IDF 6.1); runs on an
ESP32-P4-WIFI6 dev kit (init, 8x8 @ 15 Hz, TinyUSB on the HS port); page
rendered in Chrome with a synthetic frame; node web-app test + host test pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU
… VL53L5CX status

The ESP32-P4 has two internal full-speed PHYs. At power-on the USB-Serial-JTAG
owns PHY 0 (GPIO 24 / 25, the dev kits' USB-C) and USB-OTG 1.1 is wired to
PHY 1 (GPIO 26 / 27); IDF never swaps them, so a full-speed TinyUSB device on
`Config::port` 0 enumerated on pads no dev kit routes. `UsbDevice::Config::fs_phy`
(default 0) now maps the OTG 1.1 controller onto the USB-C pads before the
driver is installed (`usb_wrap_ll_phy_select`); -1 keeps IDF's mapping.
usb_device PRIV_REQUIRES esp_hal_usb on IDF 6.

The VL53L5CX webapp example defaults to the full-speed port on the P4 dev
kits (their USB-C), and `Vl53l5cxService` reads the settings back from the
sensor on every STATUS, also while ranging (the firmware only refuses writes),
so the boot-time settings are reported correctly.

Verified on an ESP32-P4-WIFI6 dev kit over the CDC interface: discovery,
every request (OK / ERROR paths), 4x4 @ 30 Hz streaming with no gaps, 8x8 @
15 Hz with divider 3 + motion indicator, snapshot, re-initialize; esp32s3
still builds.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU
…lot low-confidence zones

A zone whose target status is 255 (no target detected) still carries the
zone's last distance in the frame, so the console showed e.g. "3600 mm" in the
zone panel while (correctly) plotting nothing. Tier the statuses the way
UM2884 describes them: 5 / 9 valid, 6 / 10 / 12 a range with lower confidence
(now plotted as hollow / faded points and dashed cells; a "Show" selector
restricts to valid only or widens to any target), 255 "no target" (cell reads
"none", the panel says "no target (last N mm)"), anything else a failed
measurement (value in brackets, not plotted). The frame line counts each
class and a hint under the grid explains that no-target zones mean the return
signal was too weak at that distance, with the settings that extend the reach.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU
The auto range followed each frame's min / max, so a few millimetres of
noise re-coloured every zone. The range is now a mode: "sensor scale" (the
default; the metric's full scale, 0-4000 mm for the distance, 0-100 % for the
reflectance, 0-32 mm for the sigma) so a value always has the same colour;
"auto", which widens at once but narrows only 5 % per frame and rounds its
ends to a tidy step; and "manual". The 3D scene's framing distance is
tracked the same way, so the view no longer zooms with every frame.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU
…ster the "sensors" app category everywhere

A "Trail" control (seconds, default 2, 0 = off) keeps the zone positions of
the previous frames in the 3D view and fades them out with age: in the
surface view as small markers behind the current frame (not pickable), in
the bars view as a zone's last seen height while it currently reports no
target. Intermittent far zones stay visible between detections and a moving
target leaves a trail; a timer keeps the fade going between frames.

The new "sensors" category was missing from the apps-registry host test's
category list (the CI web lint) and from one list in the custom-modules
guide; both now match doc/generate_apps_index.py.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU
@finger563

Copy link
Copy Markdown
Contributor Author

VL53L5CX console media for the docs (live data from the VL53L5CX on the ESP32-P4-WIFI6 dev kit, 8x8 @ 15 Hz, replayed through the page).

Surface view (zones at their measured positions in the field of view, coloured by distance on the fixed sensor scale):

vl53l5cx_console_surface

Bars view (height = distance, hovered zone's details in the Zone panel):

vl53l5cx_console_bars

Live (orbit, bars, colour by signal):

vl53l5cx_console_live

finger563 and others added 2 commits October 19, 2026 16:25
…e READMEs and docs

Surface view, bars view and a short recording (orbit, bars, colour by
signal) of the hosted console showing live data from a VL53L5CX on an
ESP32-P4-WIFI6 dev kit (8x8 at 15 Hz), hosted as PR #846 attachments and
linked from the component and example READMEs, the distance-sensor page
and the web-apps page.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU
…B-C pads need fs_phy

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFjjnq3XCTRAXENSxsCxJU
@finger563
finger563 merged commit a1654fe into main Oct 19, 2026
177 checks passed
@finger563
finger563 deleted the feat/vl53l5cx branch October 19, 2026 21:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants