Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4e611d9
feat(serial_plotter): add espp::Telemetry binary telemetry service + …
finger563 Sep 9, 2026
b7c613f
feat(serial_plotter): WebUSB binary telemetry transport in the web ap…
finger563 Sep 9, 2026
2c2db79
fix(serial_plotter): address telemetry review + static analysis
finger563 Sep 9, 2026
2e76177
fix(serial_plotter): address telemetry review round 2
finger563 Sep 9, 2026
9903fe8
docs(serial_plotter): ready-message says WebUSB only (example is WebU…
finger563 Sep 9, 2026
ab18f8d
fix(serial_plotter): make the two connect buttons a clear pair
finger563 Sep 9, 2026
dfb4227
fix(serial_plotter): robust WebUSB (re)connect handshake
finger563 Sep 9, 2026
ec159be
feat(serial_plotter): live zoom / Follow mode
finger563 Sep 9, 2026
6ff3270
fix(serial_plotter): label units (Window s / Retain samples) + device…
finger563 Sep 9, 2026
1e0b1b6
fix(serial_plotter): keep WebUSB device timestamp absolute (don't reb…
finger563 Sep 9, 2026
bd8a1ea
feat(serial_plotter): offer Arrival time as an X source over WebUSB too
finger563 Sep 9, 2026
d9d63ea
fix(serial_plotter): stop the X-source dropdown flashing while streaming
finger563 Sep 9, 2026
a33e2f0
fix(serial_plotter): base WebUSB "Arrival time" on the device clock, …
finger563 Sep 9, 2026
8f3e0cf
update to use to timer instead of task for better sample timing
finger563 Sep 9, 2026
3f1a1a6
refactor(serial_plotter): rename WebUSB relative X source to "Elapsed…
finger563 Sep 9, 2026
b638e74
fix(serial_plotter): Follow Window is always seconds, in any X source
finger563 Sep 9, 2026
250d90c
feat(serial_plotter): pan/zoom the plot when not following
finger563 Sep 9, 2026
d8d27f1
feat(serial_plotter): live-scroll when not following + finer wheel zoom
finger563 Sep 9, 2026
419c174
feat(serial_plotter): settings popover with wheel-zoom sensitivity co…
finger563 Sep 9, 2026
3f9f078
fix(serial_plotter): address PR review (schema/sample race, WebUSB ro…
finger563 Sep 9, 2026
57ceed5
refactor(telemetry)!: rename serial_plotter component to telemetry
finger563 Sep 9, 2026
42d0a0b
fix(telemetry): address PR review + strip ANSI/% from serial header l…
finger563 Sep 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,9 @@ jobs:
target: esp32
- path: 'components/task/example'
target: esp32
- path: 'components/telemetry/example'
target: esp32s3
command: 'IDF_COMPONENT_MANAGER=0 idf.py build'
- path: 'components/thermistor/example'
target: esp32
- path: 'components/thread_pool/example'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/upload_components.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ jobs:
components/runqueue
components/rx8130ce
components/seeed-studio-round-display
components/serial_plotter
components/serialization
components/smartpanlee-sc01-plus
components/socket
Expand All @@ -171,6 +170,7 @@ jobs:
components/t-dongle-s3
components/tabulate
components/task
components/telemetry
components/thermistor
components/thread_pool
components/timer
Expand Down
8 changes: 0 additions & 8 deletions components/serial_plotter/CMakeLists.txt

This file was deleted.

17 changes: 0 additions & 17 deletions components/serial_plotter/idf_component.yml

This file was deleted.

11 changes: 11 additions & 0 deletions components/telemetry/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# The telemetry component ships:
# - web/telemetry.html: the browser data plotter (Web Serial + WebUSB),
# auto-hosted by the docs "web apps" pipeline and usable offline over file://.
# - include/telemetry.hpp: espp::Telemetry, a binary telemetry emitter
# (dispatcher module 3 over stream_frame) that the web app plots over WebUSB.
# The header only depends on base_component + stream_frame; an app pairs it with
# a Dispatcher and a transport (e.g. usb_device) — see the example.
idf_component_register(
INCLUDE_DIRS "include"
REQUIRES base_component stream_frame
)
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
# Serial Plotter
# Telemetry

A self-contained browser tool for reading columnar serial data and plotting it
efficiently — modeled on [esp-cpp/uart_serial_plotter](https://github.com/esp-cpp/uart_serial_plotter),
but running entirely in a Chromium-based browser over the Web Serial API. No
install, no CDN, no network access.
The `telemetry` component pairs a firmware-side `espp::Telemetry` emitter with a
self-contained browser **Serial Plotter** web app that reads data and plots it
efficiently — modeled on
[esp-cpp/uart_serial_plotter](https://github.com/esp-cpp/uart_serial_plotter),
but running entirely in a Chromium-based browser. No install, no CDN, no network
access. Two transports feed the same plot:

- **Hosted:** <https://esp-cpp.github.io/espp/apps/serial_plotter.html>
- **Offline:** open `web/serial_plotter.html` directly via a `file://` URL.
- **Web Serial (text / CSV)** — auto-parses columnar output (a header line plus
numeric rows) from any device that prints it.
- **WebUSB (binary telemetry)** — an espp device streams typed float channels
via `espp::Telemetry` (see `include/telemetry.hpp` and the
[example](example/)) for higher rate and device-accurate timestamps.

> This component currently ships the webapp only. A firmware-side binary
> **telemetry** transport (a `stream_frame` / `dispatcher` module for
> higher-bandwidth, typed channels) is a planned follow-up; the same webapp will
> gain a WebUSB transport that feeds the same plot.
- **Hosted:** <https://esp-cpp.github.io/espp/apps/telemetry.html>
- **Offline:** open `web/telemetry.html` directly via a `file://` URL.

## Screenshots

Expand Down Expand Up @@ -47,6 +50,11 @@ rest, with the per-series filter bar:
(`Float32Array`) and are drawn with [uPlot](https://github.com/leeoniya/uPlot),
which does the pixel decimation. Redraws are coalesced to one per animation
frame. The retained-points cap is configurable (default 200k per series).
- **Live zoom (Follow).** While streaming, the view auto-scrolls to the latest
data — optionally to a rolling **Window** of the last _N_ seconds. Drag to zoom
and it drops out of **Follow** so your zoomed view stays put (all retained
samples remain there to pan/zoom through); double-click, or click **Follow**,
to snap back to live.
- **Series filter.** A filter bar shows a colored chip per column: click to
toggle a series on/off, or type in the name box to plot only the columns /
tags that match (composes with the manual toggles), plus **All** / **None**.
Expand All @@ -60,16 +68,23 @@ rest, with the per-series filter bar:
(or any matching CSV) to view it offline with no device connected.
- **Serial controls.** Baud selector, pause / resume, clear, and a DTR/RTS device
reset.
- **Binary telemetry over WebUSB.** Connect with **USB** to an espp device
running `espp::Telemetry`: the app reads the channel schema and plots the
device-timestamped sample stream (decoded from the `stream_frame` framing,
dispatcher module 3) into the same plot. Requests the schema on connect and
can pause/resume the device stream.

## Requirements

Web Serial is available only in Chromium-based browsers (Chrome, Edge, Opera) and
needs a secure context — it works from `https`, `http://localhost`, or `file://`.
In an unsupported browser the app still loads and can **Load CSV** for viewing.
Web Serial and WebUSB are available only in Chromium-based browsers (Chrome,
Edge, Opera) and need a secure context — they work from `https`,
`http://localhost`, or `file://`. In an unsupported browser the app still loads
and can **Load CSV** for viewing. Native USB telemetry needs an ESP32-S3 (also
S2 / P4) device; see [`example/`](example/).

## Third-party

Plotting uses **uPlot** (`web/uPlot.iife.min.js`), MIT-licensed, pinned to
v1.6.31 — <https://github.com/leeoniya/uPlot>. It is vendored as a sibling `.js`
file (the docs workflow ships `web/*.html` and `web/*.js`); uPlot's small CSS is
inlined into `serial_plotter.html`. Everything else is dependency-free.
inlined into `telemetry.html`. Everything else is dependency-free.
51 changes: 51 additions & 0 deletions components/telemetry/example/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.20)

# Build the example (and the espp components it pulls in) as C++20 (the repo
# standard; the telemetry service needs no C++23 feature). Must be set before
# the project.cmake include / project() call so IDF picks it up.
set(CMAKE_CXX_STANDARD 20)

# NOTE: the IDF component manager is intentionally left ENABLED here (unlike most
# espp examples) so that it can fetch the managed `espressif/esp_tinyusb`
# dependency declared by the usb_device component's idf_component.yml. To avoid
# the component manager scanning every espp component manifest (some board
# components declare target-specific constraints that would fail on esp32s3),
# EXTRA_COMPONENT_DIRS is narrowed to just the components this example uses; the
# in-repo espp components there satisfy the `espp/*` dependencies locally.
include($ENV{IDF_PATH}/tools/cmake/project.cmake)

# add only the component directories that we want to use
set(EXTRA_COMPONENT_DIRS
"../../../components/base_component"
"../../../components/dispatcher"
"../../../components/format"
"../../../components/logger"
"../../../components/telemetry"
"../../../components/stream_frame"
"../../../components/task"
"../../../components/timer"
"../../../components/usb_device"
)

# With the component manager disabled (IDF_COMPONENT_MANAGER=0, e.g. in CI so the
# build does not need the as-yet unpublished espp/* components in the registry),
# esp_tinyusb/tinyusb are not fetched; add the vendored submodule copies under
# external/ to the search path. esp_tinyusb's CMakeLists adds `tinyusb` to its
# REQUIRES when the manager is off, so both directories must be discoverable.
if(DEFINED ENV{IDF_COMPONENT_MANAGER} AND "$ENV{IDF_COMPONENT_MANAGER}" STREQUAL "0")
list(APPEND EXTRA_COMPONENT_DIRS
"../../../external/esp-usb/device/esp_tinyusb"
"../../../external/tinyusb"
)
endif()

set(
COMPONENTS
"main esptool_py base_component dispatcher format logger telemetry stream_frame task timer usb_device esp_tinyusb"
CACHE STRING
"List of components to include"
)

project(telemetry_example)
46 changes: 46 additions & 0 deletions components/telemetry/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Telemetry — USB example (→ Serial Plotter web app)

Streams synthetic float channels from an ESP32-S3 to the browser **Serial
Plotter** web app over USB, using `espp::Telemetry` (a binary telemetry emitter
carried on the `stream_frame` framing, dispatcher module 3).

The hosted app — <https://esp-cpp.github.io/espp/apps/telemetry.html> —
connects on the **vendor (WebUSB)** interface, reads the channel **schema**, and
plots the live **sample** stream. It is the binary, higher-rate,
device-timestamped counterpart to the app's text/CSV Web Serial transport.

## What it does

- Declares four channels — `sine`, `cosine`, `noise`, `ramp` — as the schema.
- A producer task emits one sample (a `float` per channel) every ~10 ms (100 Hz),
timestamped with the device clock.
- Exposes the stream over the USB **vendor (WebUSB)** interface; a `Dispatcher`
routes module-3 frames to the emitter and serves capability discovery so the
browser **Device Hub** lists this device and links to `telemetry.html`.
- The web app can pause/resume the stream and request a rate (`SET_STREAM`), and
requests the schema on connect (`GET_SCHEMA`).

`espp::Telemetry` itself is transport-agnostic (the `stream_frame` framing works
over CDC / UART / a socket too); this example streams over WebUSB because that is
what the web app's binary path consumes.

Swap the synthetic generator for your real signals: build a `std::array<float, N>`
in channel order and call `telemetry.emit(...)`.

## Build & run

```sh
idf.py -p /dev/ttyACM0 flash monitor # target esp32s3 (set in sdkconfig.defaults)
```

Then open the Serial Plotter web app, click **Connect (USB)**, and pick the
"espp Serial Plotter" device. The system console/logs go to the separate
built-in USB-Serial-JTAG.

## Notes

- Native USB (vendor / WebUSB) needs an ESP32-S3 (also S2 / P4) — not the
classic ESP32. `sdkconfig.defaults` pins `esp32s3` and enables the TinyUSB
vendor class.
- WebUSB / Web Serial are Chromium-only and need a secure context (`https`,
`http://localhost`, or `file://`).
5 changes: 5 additions & 0 deletions components/telemetry/example/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
idf_component_register(
SRC_DIRS "."
INCLUDE_DIRS "."
REQUIRES telemetry dispatcher stream_frame task timer usb_device esp_tinyusb
)
Loading
Loading