Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 10 additions & 8 deletions .github/workflows/build_and_publish_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,22 +53,24 @@ jobs:
# Host the interactive single-file web apps (Web Serial / WebUSB /
# WebHID tools) alongside the docs. GitHub Pages serves over https
# (a secure context), so the browser APIs work directly from these
# hosted copies.
# hosted copies. ANY component's web/ directory is hosted
# automatically -- drop an .html (plus optional same-origin .js
# assets) into components/<name>/web/ and it ships.
mkdir -p ../docs/apps
# nullglob makes a "no matches" case a no-op (the glob expands to
# nothing) while a genuine copy error still fails the job, instead
# of being swallowed by `|| true`.
shopt -s nullglob
consoles=(../components/odrive_ascii/web/*.html)
if [ ${#consoles[@]} -gt 0 ]; then
cp "${consoles[@]}" ../docs/apps/
apps=(../components/*/web/*.html ../components/*/web/*.js)
if [ ${#apps[@]} -gt 0 ]; then
cp "${apps[@]}" ../docs/apps/
else
echo "No hosted web consoles found to copy." >&2
echo "No hosted web apps found to copy." >&2
fi
for f in ../components/usb_device/web/*.html ../components/usb_device/web/*.js; do
cp "$f" ../docs/apps/.
done
shopt -u nullglob
# Generate the landing page (docs/apps/index.html) listing every
# hosted app by its <title> + <meta name="description">.
python3 generate_apps_index.py ../docs/apps

- name: Build Documentation (PDF)
run: |
Expand Down
1 change: 1 addition & 0 deletions components/odrive_ascii/web/hid_visualizer.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WebHID Gamepad Input Visualizer</title>
<meta name="description" content="WebHID input visualizer: connect any HID gamepad or device and watch its decoded buttons, sticks, and raw input reports live.">
<!--
WebHID Gamepad Input Visualizer
===============================
Expand Down
1 change: 1 addition & 0 deletions components/odrive_ascii/web/odrive_console.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ODrive ASCII Web Serial Console</title>
<meta name="description" content="Web Serial console for the ODrive ASCII protocol: interactive terminal, quick motor controls, and live position/velocity plotting.">
<!--
ODrive ASCII Web Serial Console
================================
Expand Down
1 change: 1 addition & 0 deletions components/odrive_ascii/web/odrive_control_panel.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ODrive Native WebUSB Control Panel</title>
<meta name="description" content="WebUSB control panel for the ODrive native (Fibre) binary protocol: endpoint tree browser, live multi-signal plots, and quick controls.">
<!--
ODrive Native (Fibre endpoint) WebUSB Control Panel
===================================================
Expand Down
1 change: 1 addition & 0 deletions components/odrive_ascii/web/odrive_webusb_console.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ODrive ASCII WebUSB Console</title>
<meta name="description" content="WebUSB console speaking the ODrive ASCII protocol over the vendor bulk interface (no serial driver needed).">
<!--
ODrive ASCII WebUSB Console
===========================
Expand Down
1 change: 1 addition & 0 deletions components/usb_device/web/board_console.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>espp Board Console &amp; ESP Flasher</title>
<meta name="description" content="General-purpose Web Serial board console and ESP flasher (esptool-js): serial monitor, reset/bootloader controls, and firmware flashing.">
<!--
espp Board Console + ESP Flasher (Web Serial)
=============================================
Expand Down
4 changes: 3 additions & 1 deletion doc/en/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ abstractions for building applications from reusable components.

New here? Start with :doc:`getting_started`. The APIs below are grouped by
capability; all supported development boards are collected under
:doc:`dev_boards/index`.
:doc:`dev_boards/index`. Browser tools for interacting with your hardware are
collected under :doc:`web_apps`.

.. toctree::
:maxdepth: 1
:caption: Introduction

getting_started
web_apps

.. toctree::
:maxdepth: 1
Expand Down
32 changes: 32 additions & 0 deletions doc/en/web_apps.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Web Apps
********

espp ships a growing set of **self-contained browser tools** that talk directly
to your hardware using the Web Serial / WebUSB / WebHID APIs (Chromium-based
browsers) — nothing to install. They are hosted alongside this documentation:

**→** `esp-cpp.github.io/espp/apps <https://esp-cpp.github.io/espp/apps/index.html>`_

Highlights:

- **Board Console & ESP Flasher** — a general-purpose Web Serial monitor with
reset / bootloader controls and an `esptool-js`-based firmware flasher.
- **ODrive ASCII Web Serial console** and **WebUSB console** — interactive
terminals with quick motor controls and live plotting for the
:doc:`odrive_ascii <motor_control/odrive_ascii>` protocol server.
- **ODrive Native WebUSB control panel** — endpoint-tree browser, live
multi-signal plots, and typed read/write for the ODrive native (Fibre)
binary protocol.
- **WebHID input visualizer** — decoded buttons / sticks / raw reports for any
HID device, driven entirely by its report descriptor.

Adding a new app
----------------

Any single-file app placed in a component's ``web/`` directory
(``components/<name>/web/*.html``, plus optional same-origin ``.js`` assets) is
hosted automatically by the docs workflow, and the `apps landing page
<https://esp-cpp.github.io/espp/apps/index.html>`_ lists it using the file's
``<title>`` and ``<meta name="description">`` tags — no registry to maintain.
Apps must be fully self-contained (no CDN resources) so they work offline and
under GitHub Pages' strict hosting.
102 changes: 102 additions & 0 deletions doc/generate_apps_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""Generate docs/apps/index.html from the hosted single-file web apps.

Run by .github/workflows/build_and_publish_docs.yml AFTER the per-component
web apps (components/*/web/*.html) are copied into docs/apps/. Each app's
card title comes from its <title> and its blurb from
<meta name="description" content="...">, so a new app is listed automatically
by simply having those two tags -- no registry to maintain.

Usage: generate_apps_index.py <apps_dir>
"""

import html
import re
import sys
from pathlib import Path


def extract(path: Path):
text = path.read_text(encoding="utf-8", errors="replace")
title_m = re.search(r"<title>(.*?)</title>", text, re.S | re.I)
desc_m = re.search(
r'<meta\s+name=["\']description["\']\s+content=["\'](.*?)["\']', text, re.S | re.I)
title = html.unescape(title_m.group(1).strip()) if title_m else path.stem
desc = html.unescape(desc_m.group(1).strip()) if desc_m else ""
Comment on lines +16 to +25
return title, desc


def main() -> int:
apps_dir = Path(sys.argv[1])
Comment on lines +29 to +30
apps = sorted(p for p in apps_dir.glob("*.html") if p.name != "index.html")
if not apps:
print(f"no apps found in {apps_dir}", file=sys.stderr)
return 1

cards = []
for app in apps:
title, desc = extract(app)
cards.append(
f' <a class="card" href="{html.escape(app.name)}">\n'
f" <h2>{html.escape(title)}</h2>\n"
f" <p>{html.escape(desc) if desc else '&nbsp;'}</p>\n"
Comment on lines +39 to +42
f" </a>")
print(f" indexed: {app.name} -> {title}")

page = f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>espp Web Apps</title>
<meta name="description" content="Browser tools hosted with the espp documentation: Web Serial / WebUSB / WebHID consoles, control panels, and flashers.">
<style>
:root {{
--bg: #ffffff; --fg: #1a1a2e; --muted: #666; --card: #f6f7f9;
--border: #d9dce1; --accent: #2563eb;
}}
@media (prefers-color-scheme: dark) {{
:root {{
--bg: #14161a; --fg: #e6e6e6; --muted: #9aa0a6; --card: #1d2127;
--border: #333842; --accent: #7aa2ff;
}}
}}
* {{ box-sizing: border-box; }}
body {{ margin: 0; padding: 2rem 1rem; background: var(--bg); color: var(--fg);
font: 16px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif; }}
main {{ max-width: 60rem; margin: 0 auto; }}
h1 {{ margin: 0 0 .25rem; }}
.sub {{ color: var(--muted); margin: 0 0 2rem; }}
.grid {{ display: grid; grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); gap: 1rem; }}
.card {{ display: block; padding: 1rem 1.25rem; background: var(--card);
border: 1px solid var(--border); border-radius: .6rem;
color: inherit; text-decoration: none; }}
.card:hover {{ border-color: var(--accent); }}
.card h2 {{ margin: 0 0 .4rem; font-size: 1.05rem; color: var(--accent); }}
.card p {{ margin: 0; color: var(--muted); font-size: .92rem; }}
footer {{ margin-top: 2.5rem; color: var(--muted); font-size: .85rem; }}
footer a {{ color: var(--accent); }}
</style>
</head>
<body>
<main>
<h1>espp Web Apps</h1>
<p class="sub">Self-contained browser tools hosted with the espp documentation.
They use the Web&nbsp;Serial / WebUSB / WebHID APIs (Chromium-based browsers)
and talk directly to your hardware &mdash; nothing to install.</p>
<div class="grid">
{chr(10).join(cards)}
</div>
<footer>Part of the <a href="../index.html">espp documentation</a> &middot;
<a href="https://github.com/esp-cpp/espp">esp-cpp/espp</a></footer>
</main>
</body>
</html>
"""
(apps_dir / "index.html").write_text(page, encoding="utf-8")
print(f"wrote {apps_dir / 'index.html'} ({len(apps)} apps)")
return 0


if __name__ == "__main__":
sys.exit(main())
Loading