DeterministicESPAsyncWebServer v6.28.0
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
web_terminal.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file web_terminal.cpp
6 * @brief Browser web-serial terminal over WebSocket (DETWS_ENABLE_WEB_TERMINAL).
7 */
8
10
11#if DETWS_ENABLE_WEB_TERMINAL
12
13// Dependency (WEB_TERMINAL requires WEBSOCKET) is enforced centrally in ServerConfig.h.
14
15#include "network_drivers/application/web_assets.h" // DETWS_TERMINAL_PAGE
17#include <stdarg.h>
18#include <stdio.h>
19#include <string.h>
20
21// ---------------------------------------------------------------------------
22// State (all static / BSS - no heap)
23// ---------------------------------------------------------------------------
24// All web-terminal state, owned by one instance (internal linkage): the server handle, the
25// command callback, the WebSocket path, and which ws slots are terminal browsers. Grouped so
26// it is one named owner, unreachable cross-TU. (The route/ws handlers are fixed-signature
27// callbacks, so they reach this single owner directly.)
28struct WebTerminalCtx
29{
30 DetWebServer *srv = nullptr;
31 TermCommandCb cb = nullptr;
32 char ws_path[MAX_PATH_LEN] = {0};
33 bool is_client[MAX_WS_CONNS] = {}; // which ws slots are terminal browsers
34};
35static WebTerminalCtx s_term;
36
37// ---- internal route handlers ----------------------------------------------
38
39static void term_page_handler(uint8_t slot_id, HttpReq *req)
40{
41 (void)req;
42 if (s_term.srv)
43 s_term.srv->send(slot_id, 200, DET_MIME_TEXT_HTML, DETWS_TERMINAL_PAGE);
44}
45
46static void term_ws_connect(uint8_t ws_id)
47{
48 if (ws_id < MAX_WS_CONNS)
49 s_term.is_client[ws_id] = true;
50 if (s_term.srv)
51 s_term.srv->ws_send_text(ws_id, "DeterministicESPAsyncWebServer terminal ready\n");
52}
53
54static void term_ws_message(uint8_t ws_id)
55{
56 if (s_term.cb && ws_id < MAX_WS_CONNS)
57 s_term.cb(ws_payload(ws_id), ws_id);
58}
59
60static void term_ws_close(uint8_t ws_id)
61{
62 if (ws_id < MAX_WS_CONNS)
63 s_term.is_client[ws_id] = false;
64}
65
66// ---- public API -----------------------------------------------------------
67
68void detws_web_terminal_begin(DetWebServer &server, const char *path)
69{
70 s_term.srv = &server;
71 for (uint8_t i = 0; i < MAX_WS_CONNS; i++)
72 s_term.is_client[i] = false;
73
74 if (!path || !path[0])
75 path = "/terminal";
76 snprintf(s_term.ws_path, sizeof(s_term.ws_path), "%s/ws", path);
77
78 server.on(path, HttpMethod::HTTP_GET, term_page_handler);
79 server.on_ws(s_term.ws_path, term_ws_connect, term_ws_message, term_ws_close);
80}
81
82void detws_web_terminal_on_command(TermCommandCb cb)
83{
84 s_term.cb = cb;
85}
86
87void detws_web_terminal_print(const char *s)
88{
89 if (!s_term.srv || !s)
90 return;
91 for (uint8_t i = 0; i < MAX_WS_CONNS; i++)
92 {
93 if (s_term.is_client[i] && ws_active(i))
94 s_term.srv->ws_send_text(i, s);
95 }
96}
97
98void detws_web_terminal_println(const char *s)
99{
100 char buf[TERM_TX_BUF_SIZE];
101 snprintf(buf, sizeof(buf), "%s\n", s ? s : "");
102 detws_web_terminal_print(buf);
103}
104
105void detws_web_terminal_printf(const char *fmt, ...)
106{
107 char buf[TERM_TX_BUF_SIZE];
108 va_list ap;
109 va_start(ap, fmt);
110 vsnprintf(buf, sizeof(buf), fmt, ap);
111 va_end(ap);
112 detws_web_terminal_print(buf);
113}
114
115uint8_t detws_web_terminal_client_count()
116{
117 uint8_t n = 0;
118 for (uint8_t i = 0; i < MAX_WS_CONNS; i++)
119 if (s_term.is_client[i] && ws_active(i))
120 n++;
121 return n;
122}
123
124#endif // DETWS_ENABLE_WEB_TERMINAL
#define TERM_TX_BUF_SIZE
Stack scratch for detws_web_terminal_printf()/println() formatting.
#define MAX_PATH_LEN
Maximum URL path length (including leading /).
#define MAX_WS_CONNS
Maximum simultaneous WebSocket connections.
Single-port HTTP server with deterministic, zero-allocation execution.
Definition dwserver.h:348
void on(const char *path, HttpMethod method, Handler callback)
Register a route handler.
Definition dwserver.cpp:759
@ HTTP_GET
Safe, idempotent read.
Shared HTTP Content-Type ("MIME") string constants (one source of truth).
Fully-parsed HTTP/1.1 request.
const char DETWS_TERMINAL_PAGE[]
Self-contained WebSocket terminal page (green-phosphor CRT theme; auto ws/wss).
Layer 7 (Application) - embedded web assets generated from src/web/input/.
Browser "web serial" terminal over WebSocket (DETWS_ENABLE_WEB_TERMINAL).
void(* TermCommandCb)(const char *line, uint8_t client_id)
const char * ws_payload(uint8_t ws_id)
The NUL-terminated reassembled message payload for ws_id, or nullptr if the slot is out of range / in...
Definition websocket.cpp:45
bool ws_active(uint8_t ws_id)
True if ws_id is a valid, in-use WebSocket slot. Use this instead of reaching into ws_pool[ws_id]....
Definition websocket.cpp:40