ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
dashboard_routes.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 dashboard_routes.cpp
6 * @brief Dashboard server wiring: page, layout JSON, and SSE value stream.
7 *
8 * Separated from the host-testable core (dashboard.cpp) so the serializers can be
9 * unit-tested without pulling in the server. Requires PC_ENABLE_SSE.
10 */
11
13#include "shared_primitives/strbuf.h" // pc_sb frame builder
14
15#if PC_ENABLE_DASHBOARD
16
17// Dependency (DASHBOARD requires SSE) is enforced centrally in protocore_config.h.
18
19#include "network_drivers/application/web_assets.h" // PC_DASHBOARD_PAGE
20#include "protocore.h"
22#include <stdio.h>
23#if PC_ENABLE_WEBSOCKET
24#include "network_drivers/presentation/http/websocket/websocket.h" // ws_pool for inbound control messages
25#endif
26
27// All dashboard-routes state, owned by one instance (internal linkage): the server handle and
28// the SSE / WebSocket paths, grouped so it is one named owner, unreachable cross-TU. (The route
29// handlers are fixed-signature callbacks, so they reach this single owner directly.)
30struct DashRoutesCtx
31{
32 PC *srv = nullptr;
33 char stream_path[MAX_PATH_LEN] = {0};
34#if PC_ENABLE_WEBSOCKET
35 char ws_path[MAX_PATH_LEN] = {0};
36#endif
37};
38static DashRoutesCtx s_dashr;
39
40static void dash_page_handler(uint8_t slot_id, HttpReq *req)
41{
42 (void)req;
43 if (s_dashr.srv)
44 {
45 s_dashr.srv->send(slot_id, 200, PC_MIME_TEXT_HTML, PC_DASHBOARD_PAGE);
46 }
47}
48
49static void dash_layout_handler(uint8_t slot_id, HttpReq *req)
50{
51 (void)req;
52 char buf[PC_DASHBOARD_JSON_BUF];
53 pc_dashboard_layout_json(buf, sizeof(buf));
54 if (s_dashr.srv)
55 {
56 s_dashr.srv->send(slot_id, 200, PC_MIME_JSON, buf);
57 }
58}
59
60static void dash_sse_connect(uint8_t pc_sse_id)
61{
62 char buf[PC_DASHBOARD_JSON_BUF];
63 if (s_dashr.srv && pc_dashboard_values_json(buf, sizeof(buf)) > 0)
64 {
65 s_dashr.srv->pc_sse_send(pc_sse_id, buf); // seed the new client with the latest values
66 }
67}
68
69#if PC_ENABLE_WEBSOCKET
70static void dash_ws_connect(uint8_t ws_id)
71{
72 (void)ws_id;
73}
74static void dash_ws_message(uint8_t ws_id)
75{
76 // Control widgets send {"k":"<key>","v":<num>}; parse + dispatch to the callback.
77 if (ws_id < MAX_WS_CONNS)
78 {
79 pc_dashboard_dispatch_control(ws_payload(ws_id));
80 }
81}
82static void dash_ws_close(uint8_t ws_id)
83{
84 (void)ws_id;
85}
86#endif
87
88void pc_dashboard_begin(PC &server, const char *path, const pc_widget *widgets, uint8_t count)
89{
90 s_dashr.srv = &server;
91 pc_dashboard_configure(widgets, count);
92
93 if (!path || !path[0])
94 {
95 path = "/dashboard";
96 }
97
98 char layout_path[MAX_PATH_LEN];
99 pc_sb sb_layout_path = {layout_path, sizeof(layout_path), 0, true};
100 pc_sb_put(&sb_layout_path, path);
101 pc_sb_put(&sb_layout_path, "/layout");
102 if (pc_sb_finish(&sb_layout_path) == 0)
103 {
104 layout_path[0] = '\0';
105 }
106 pc_sb sb_stream_path = {s_dashr.stream_path, sizeof(s_dashr.stream_path), 0, true};
107 pc_sb_put(&sb_stream_path, path);
108 pc_sb_put(&sb_stream_path, "/stream");
109 if (pc_sb_finish(&sb_stream_path) == 0)
110 {
111 s_dashr.stream_path[0] = '\0';
112 }
113
114 server.on(path, HttpMethod::HTTP_GET, dash_page_handler);
115 server.on(layout_path, HttpMethod::HTTP_GET, dash_layout_handler);
116 server.on_sse(s_dashr.stream_path, dash_sse_connect);
117#if PC_ENABLE_WEBSOCKET
118 pc_sb sb_ws_path = {s_dashr.ws_path, sizeof(s_dashr.ws_path), 0, true};
119 pc_sb_put(&sb_ws_path, path);
120 pc_sb_put(&sb_ws_path, "/ws");
121 if (pc_sb_finish(&sb_ws_path) == 0)
122 {
123 s_dashr.ws_path[0] = '\0';
124 }
125 server.on_ws(s_dashr.ws_path, dash_ws_connect, dash_ws_message, dash_ws_close);
126#endif
127}
128
129void pc_dashboard_publish()
130{
131 if (!s_dashr.srv)
132 {
133 return;
134 }
135 char buf[PC_DASHBOARD_JSON_BUF];
136 if (pc_dashboard_values_json(buf, sizeof(buf)) > 0)
137 {
138 s_dashr.srv->pc_sse_broadcast(s_dashr.stream_path, buf);
139 }
140}
141
142#endif // PC_ENABLE_DASHBOARD
#define MAX_WS_CONNS
Definition c2_defaults.h:72
Single-port HTTP server with deterministic, zero-allocation execution.
Definition protocore.h:348
void on(const char *path, HttpMethod method, Handler callback)
Register a route handler.
Real-time SVG telemetry dashboard (PC_ENABLE_DASHBOARD).
Shared HTTP Content-Type ("MIME") string constants (one source of truth).
Layer 7 (Application) - public HTTP routing API.
@ HTTP_GET
Safe, idempotent read.
#define PC_DASHBOARD_JSON_BUF
Stack buffer for the dashboard layout / values JSON (bytes).
#define MAX_PATH_LEN
Maximum URL path length (including leading /).
Bounded no-heap string builder that fails closed on overflow (one shared copy).
size_t pc_sb_finish(pc_sb *b)
NUL-terminate and return the built length, or 0 if the build overflowed.
Definition strbuf.h:648
void pc_sb_put(pc_sb *b, const char *s)
Append NUL-terminated s; leaves the buffer untouched and clears ok if it would not fit.
Definition strbuf.h:60
Fully-parsed HTTP/1.1 request.
Bump-append target; ok latches false once an append would overflow cap.
Definition strbuf.h:30
const char PC_DASHBOARD_PAGE[]
Real-time SVG telemetry dashboard page (PC_ENABLE_DASHBOARD).
Layer 7 (Application) - embedded web assets generated from web_assets/input/.
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
Layer 6 (Presentation) – WebSocket frame parser and connection pool.