UNPKG

webserial-core

Version:

A strongly-typed, event-driven, abstract TypeScript library for the Web Serial API with custom parsers, command queue, handshake validation, and auto-reconnect.

409 lines (392 loc) 14.9 kB
<!doctype html> <html lang="en" data-theme="dark"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <title>Web Bluetooth — webserial-core demo</title> <style> :root { --accent: #a855f7; --accent-dark: #9333ea; --accent-glow: rgba(168, 85, 247, 0.12); --accent-badge: #faf5ff; } </style> <script type="module" crossorigin src="/demos/assets/web-bluetooth-yFc46qqb.js" ></script> <link rel="modulepreload" crossorigin href="/demos/assets/demo-shared-BVQKG6NC.js" /> <link rel="stylesheet" crossorigin href="/demos/assets/demo-shared-DLFsukQx.css" /> </head> <body> <!-- ── Topbar ──────────────────────────────────────────── --> <header class="topbar"> <button class="icon-btn" id="menu-btn" title="Toggle sidebar"> <i data-lucide="menu"></i> </button> <a class="topbar-brand" href="/demos/web-serial.html"> <span class="brand-icon"><i data-lucide="bluetooth"></i></span> <span class="brand-name">webserial-core</span> <span class="brand-ver">v2</span> </a> <span class="provider-pill">Bluetooth</span> <span class="flex-1"></span> <nav class="topbar-nav"> <a class="nav-item" href="/demos/web-serial.html" ><i data-lucide="zap"></i><span class="nav-lbl"> Web Serial</span></a > <a class="nav-item active" href="/demos/web-bluetooth.html" ><i data-lucide="bluetooth"></i ><span class="nav-lbl"> Bluetooth</span></a > <a class="nav-item" href="/demos/web-usb.html" ><i data-lucide="usb"></i><span class="nav-lbl"> WebUSB</span></a > <a class="nav-item" href="/demos/websocket.html" ><i data-lucide="globe"></i><span class="nav-lbl"> WebSocket</span></a > </nav> <div class="topbar-actions"> <div class="status-pill"> <div class="status-dot" id="status-dot"></div> <span id="status-text">Disconnected</span> </div> <button class="icon-btn" id="theme-btn" title="Toggle theme">🌙</button> <button class="icon-btn code-toggle" id="code-toggle-btn" title="Toggle code panel" > <i data-lucide="code-2"></i> </button> <button class="btn btn-connect" id="btn-connect">Pair Device</button> <button class="btn btn-disconnect" id="btn-disconnect" disabled> Disconnect </button> </div> </header> <!-- ── App layout ───────────────────────────────────────── --> <div class="app-layout"> <!-- ── Sidebar ─────────────────────────────────────────── --> <aside class="sidebar" id="sidebar"> <div class="sidebar-scroll"> <!-- BLE info notice --> <div class="sb-section"> <div class="notice"> Uses <strong>Nordic UART Service (NUS)</strong> over GATT. Compatible: nRF52, ESP32 BLE UART, or any NUS device (<code>6e400001-…</code>).<br /> Requires <strong>HTTPS</strong> or <code>localhost</code> + Chromium with Web Bluetooth enabled. </div> </div> <!-- Connection settings (limited for BLE) --> <div class="sb-section"> <div class="sb-title">Connection</div> <div class="field-row"> <div class="field"> <label for="cfg-bufsize">Buffer Size</label> <input type="number" id="cfg-bufsize" value="255" min="64" max="65536" /> </div> <div class="field"> <label for="cfg-timeout">Cmd Timeout (ms)</label> <input type="number" id="cfg-timeout" value="3000" min="0" /> </div> </div> <div class="field"> <label for="cfg-handshake">Handshake Timeout (ms)</label> <input type="number" id="cfg-handshake" value="2000" min="0" /> </div> <div class="notice" style="margin-top: 6px"> <strong>Baud rate</strong> is not used over BLE — link speed is negotiated at the GATT level. </div> </div> <!-- Message format --> <div class="sb-section"> <div class="sb-title">Message Format</div> <div class="field"> <label for="cfg-delim">Delimiter</label> <input type="text" id="cfg-delim" value="\n" placeholder="\n \r\n | etc." /> </div> <div class="field-row"> <div class="field"> <label for="cfg-prepend">Prepend</label> <input type="text" id="cfg-prepend" value="" placeholder="e.g. STX" /> </div> <div class="field"> <label for="cfg-append">Append</label> <input type="text" id="cfg-append" value="\n" placeholder="e.g. \n" /> </div> </div> </div> <!-- Handshake --> <div class="sb-section"> <div class="sb-title">Handshake on Connect</div> <div class="notice" style="margin-bottom: 6px"> Sent automatically right after the GATT link opens. Leave <em>Command</em> empty to skip entirely. </div> <div class="field-row"> <div class="field"> <label for="cfg-hs-cmd">Command to send</label> <input type="text" id="cfg-hs-cmd" value="" placeholder="e.g. PING\n or FF 01 A3" /> </div> <div class="field" style="flex: 0 0 62px"> <label for="cfg-hs-cmd-mode">Mode</label> <select id="cfg-hs-cmd-mode"> <option value="text" selected>TXT</option> <option value="hex">HEX</option> </select> </div> </div> <div class="field-row"> <div class="field"> <label for="cfg-hs-expect" >Expected <small>(empty = no check)</small></label > <input type="text" id="cfg-hs-expect" value="" placeholder="e.g. PONG or OK" /> </div> <div class="field" style="flex: 0 0 62px"> <label for="cfg-hs-expect-mode">Mode</label> <select id="cfg-hs-expect-mode"> <option value="text" selected>TXT</option> <option value="hex">HEX</option> </select> </div> </div> </div> <!-- Saved Commands --> <div class="sb-section"> <div class="sb-title">Saved Commands</div> <div class="notice" style="margin-bottom: 6px"> Pre-defined commands to send. Use <em>HEX</em> for raw bytes. </div> <div class="field-row"> <div class="field" style="flex: 0 0 72px"> <label for="cmd-name">Name</label> <input type="text" id="cmd-name" placeholder="LED ON" /> </div> <div class="field"> <label for="cmd-value">Command</label> <input type="text" id="cmd-value" placeholder="LED_ON\n" /> </div> <div class="field" style="flex: 0 0 62px"> <label for="cmd-mode">Mode</label> <select id="cmd-mode"> <option value="text" selected>TXT</option> <option value="hex">HEX</option> </select> </div> </div> <button class="btn btn-ghost" id="cmd-add" style="width: 100%; margin-top: 3px" > <i data-lucide="plus"></i> Add Command </button> <div class="chip-list" id="cmd-list"></div> </div> <!-- Data Listeners --> <div class="sb-section"> <div class="sb-title">Data Listeners</div> <div class="notice" style="margin-bottom: 6px"> Patterns for <code>on('serial:data')</code> — skeleton code in download. </div> <div class="field-row"> <div class="field" style="flex: 0 0 64px"> <label for="lst-name">Name</label> <input type="text" id="lst-name" placeholder="Temp" /> </div> <div class="field"> <label for="lst-pattern">Pattern</label> <input type="text" id="lst-pattern" placeholder="TEMP:" /> </div> <div class="field" style="flex: 0 0 62px"> <label for="lst-match">Match</label> <select id="lst-match"> <option value="exact">exact</option> <option value="contains" selected>has</option> <option value="startsWith">starts</option> <option value="hex">hex</option> </select> </div> </div> <button class="btn btn-ghost" id="lst-add" style="width: 100%; margin-top: 3px" > <i data-lucide="plus"></i> Add Listener </button> <div class="chip-list" id="lst-list"></div> </div> <!-- Download --> <div class="sb-section"> <div class="sb-title">Download Code</div> <div class="field"> <label for="dl-name">Class / File name</label> <input type="text" id="dl-name" value="MyBleDevice" placeholder="MyBleDevice" /> </div> <div class="dl-lang-row"> <label class="dl-opt" ><input type="radio" name="dl-lang" value="ts" checked /> TypeScript</label > <label class="dl-opt" ><input type="radio" name="dl-lang" value="js" /> JavaScript</label > </div> <div class="dl-type-row"> <label class="dl-opt" ><input type="radio" name="dl-type" value="file" /> Standalone file</label > <label class="dl-opt" ><input type="radio" name="dl-type" value="project" checked /> Full project (ZIP)</label > </div> <div class="dl-btns"> <button class="btn btn-dl" id="dl-btn"> <i data-lucide="download"></i> Download </button> <button class="btn btn-ghost" id="cfg-export-btn" title="Export configuration as JSON" > <i data-lucide="share"></i> Export config </button> <label class="btn btn-ghost" title="Import configuration from JSON" > <i data-lucide="upload"></i> Import config <input type="file" id="cfg-import-input" accept=".json" style="display: none" /> </label> </div> </div> </div> <!-- /sidebar-scroll --> </aside> <div class="sidebar-resize-handle" id="sidebar-resize-handle"></div> <!-- ── Chat console ─────────────────────────────────── --> <main class="console-area"> <div class="console-hdr"> <div class="status-pill"> <div class="status-dot" id="console-dot"></div> <span id="console-text">Pair a Bluetooth device to begin</span> </div> <button class="btn btn-ghost" id="clear-btn" style="margin-left: auto; font-size: 0.7rem; padding: 4px 10px" > Clear </button> </div> <div class="messages" id="messages"> <div class="empty-state"> <div class="empty-icon">📡</div> <span>Pair a BLE device to begin</span> </div> </div> <div class="input-bar"> <button class="btn btn-mode" id="mode-toggle">TXT</button> <input class="msg-input" id="input-send" type="text" placeholder="Type a command, e.g. LED_ON" disabled /> <button class="btn btn-send" id="btn-send" disabled>Send</button> </div> </main> <!-- ── Code panel ────────────────────────────────────── --> <div class="resize-handle" id="resize-handle"></div> <aside class="code-panel" id="code-panel"> <div class="cp-hdr"> <div class="cp-title"> <span>Code Preview</span> <span class="cp-tab" id="code-tab">device.ts</span> </div> <div class="cp-actions"> <button class="cp-btn" id="copy-btn">Copy</button> </div> </div> <pre class="code-view" id="code-view"></pre> </aside> </div> <!-- /app-layout --> <footer class="app-footer"> © 2025 <a href="https://github.com/danidoble" style="color: inherit; text-decoration: none" >@danidoble</a > · webserial-core v2 </footer> <script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script> <script> lucide.createIcons(); </script> </body> </html>