ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
dashboard.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file dashboard.h
6 * @brief Real-time SVG telemetry dashboard (PROTOCORE_ENABLE_DASHBOARD).
7 *
8 * Widgets are declared once in a fixed compile-time protocore_widget table - no heap,
9 * fixed at link. protocore_dashboard_begin() serves three things at @p path:
10 * - GET path the self-contained SVG dashboard page (from web);
11 * - GET path/layout the widget table serialized as a JSON array;
12 * - SSE path/stream a live stream of the current values.
13 * The page fetches the layout, renders one SVG widget per entry, and updates them
14 * from the SSE value stream. The application feeds readings with
15 * protocore_dashboard_set(key, value) and pushes them with protocore_dashboard_publish().
16 *
17 * The widget-table -> JSON serializers (layout + values) are pure and have no
18 * server dependency, so they unit-test on the host. Requires PROTOCORE_ENABLE_SSE.
19 *
20 * @author Douglas Quigg (dstroy0)
21 * @date 2026
22 */
23
24#ifndef PROTOCORE_DASHBOARD_H
25#define PROTOCORE_DASHBOARD_H
26
27#include "protocore_config.h" // the entry point: protocore_types.h for the widths
28
29#if PROTOCORE_ENABLE_DASHBOARD
30
32
33// PROTOCORE_DASHBOARD_BORROW - the bytes this module runs out of - is stated in protocore_config.h, which sums
34// it into its arena. A caller takes them once and passes the pointer to every call. How they
35// are carved is this module's and is never named here.
36
37/** @brief Widget rendering / interaction style. */
38typedef enum PROTO_ENUM_PACKED
39{
40 // Display widgets - updated from the SSE value stream.
41 PROTOCORE_WIDGET_VALUE = 0, ///< plain numeric readout
42 PROTOCORE_WIDGET_GAUGE, ///< radial arc gauge over [min, max]
43 PROTOCORE_WIDGET_BAR, ///< horizontal bar over [min, max]
44 PROTOCORE_WIDGET_SPARKLINE, ///< recent-history SVG line over [min, max]
45 PROTOCORE_WIDGET_CHART, ///< dense Canvas line chart over [min, max]
46 // Control widgets - send values back to the device over WebSocket.
47 PROTOCORE_WIDGET_BUTTON, ///< momentary button -> control value 1
48 PROTOCORE_WIDGET_TOGGLE, ///< on/off toggle -> control value 0/1 (reflects SSE state)
49 PROTOCORE_WIDGET_SLIDER ///< range slider over [min, max] -> control value
50} protocore_widget_type;
51
52/** @brief Control callback: invoked when a control widget sends a value over WebSocket. */
53typedef void (*protocore_control_cb)(const char *key, float value);
54
55/** @brief One dashboard widget, declared in a fixed compile-time table. */
56typedef struct
57{
58 protocore_widget_type type; ///< rendering style.
59 const char *label; ///< display label.
60 const char *key; ///< telemetry source key (matches protocore_dashboard_set()).
61 float min; ///< scale minimum (gauge / bar / sparkline).
62 float max; ///< scale maximum.
63 const char *unit; ///< unit suffix shown by the widget (may be "").
64} protocore_widget;
65
66/** @brief What configure takes: widgets, count. */
67typedef struct
68{
69 const protocore_widget *widgets;
70 uint8_t count;
71} DashboardConfigureArgs;
72
73/** @brief What set takes: key, value. */
74typedef struct
75{
76 const char *key;
77 float value;
78} DashboardSetArgs;
79
80/** @brief What layout_json takes: out, cap. */
81typedef struct
82{
83 char *out;
84 uint32_t cap;
85} DashboardLayoutJsonArgs;
86
87/** @brief What values_json takes: out, cap. */
88typedef struct
89{
90 char *out;
91 uint32_t cap;
92} DashboardValuesJsonArgs;
93
94/** @brief What on_control takes: cb. */
95typedef struct
96{
97 protocore_control_cb cb;
98} DashboardOnControlArgs;
99
100/** @brief What parse_control takes: msg, key_out, key_cap, value_out. */
101typedef struct
102{
103 const char *msg;
104 char *key_out;
105 size_t key_cap;
106 float *value_out;
107} DashboardParseControlArgs;
108
109/** @brief What dispatch_control takes: msg. */
110typedef struct
111{
112 const char *msg;
113} DashboardDispatchControlArgs;
114
115/** @brief What begin takes: path, widgets, count. */
116typedef struct
117{
118 const char *path;
119 const protocore_widget *widgets;
120 uint8_t count;
121} DashboardBeginArgs;
122
123/**
124 * @brief Real-time SVG telemetry dashboard (PROTOCORE_ENABLE_DASHBOARD).
125 *
126 * A caller sets the members a call takes, invokes it through ::Dashboard with the bytes it runs
127 * out of, and reads the outcome off the same handle.
128 *
129 * Dashboard.configure_args.widgets = ...;
130 * Dashboard.configure_args.count = ...;
131 * Dashboard.configure(work);
132 *
133 * @var DashboardNs::configure_args what configure takes: widgets, count
134 * @var DashboardNs::set_args what set takes: key, value
135 * @var DashboardNs::layout_json_args what layout_json takes: out, cap
136 * @var DashboardNs::values_json_args what values_json takes: out, cap
137 * @var DashboardNs::on_control_args what on_control takes: cb
138 * @var DashboardNs::parse_control_args what parse_control takes: msg, key_out, key_cap, value_out
139 * @var DashboardNs::dispatch_control_args what dispatch_control takes: msg
140 * @var DashboardNs::begin_args what begin takes: path, widgets, count
141 * @var DashboardNs::ok true if well-formed; writes the key (bounded by key_cap) and value
142 * @var DashboardNs::value number of characters written, or 0 if cap is too small
143 * @var DashboardNs::configure bind the widget table and reset every value to 0
144 * @var DashboardNs::set set a widget's current value by key. false if the key is unknown
145 * @var DashboardNs::layout_json serialize the widget layout as a JSON array into out
146 * @var DashboardNs::values_json serialize the current values as a JSON object {key:value,...} into ...
147 * @var DashboardNs::on_control register the callback invoked when a control widget sends a value
148 * @var DashboardNs::parse_control parse a control message `{"k":"<key>","v":<number>}` from the page
149 * @var DashboardNs::dispatch_control parse a control message and invoke the registered control callback
150 * @var DashboardNs::begin serve the dashboard at path (page, layout JSON, and SSE value ...
151 * @var DashboardNs::publish broadcast the current values to all SSE subscribers (after ...
152 *
153 * @c work is PROTOCORE_DASHBOARD_BORROW bytes the CALLER took, at an address it knows. It is not held past the call, so
154 * nothing here aliases it. How those bytes are carved is this module's and is never named here.
155 */
156typedef struct
157{
158 DashboardConfigureArgs configure_args;
159 DashboardSetArgs set_args;
160 DashboardLayoutJsonArgs layout_json_args;
161 DashboardValuesJsonArgs values_json_args;
162 DashboardOnControlArgs on_control_args;
163 DashboardParseControlArgs parse_control_args;
164 DashboardDispatchControlArgs dispatch_control_args;
165 DashboardBeginArgs begin_args;
166 proto_bool ok;
167 int32_t value;
168} DashboardVars;
169
170/** @brief The operands and the outcome. */
171extern DashboardVars DashboardV;
172
173/** @brief The entries. */
174typedef struct
175{
176 void (*const configure)(uint8_t *work);
177 void (*const set)(uint8_t *work);
178 void (*const layout_json)(uint8_t *work);
179 void (*const values_json)(uint8_t *work);
180 void (*const on_control)(uint8_t *work);
181 void (*const parse_control)(uint8_t *work);
182 void (*const dispatch_control)(uint8_t *work);
183 void (*const begin)(uint8_t *work);
184 void (*const publish)(uint8_t *work);
185} DashboardNs;
186
187// What the table binds, defined once in the .c and taking one parameter each: everything
188// else an entry needs is an operand in DashboardV or a region of the borrow at a fixed offset.
189void protocore_dashboard_configure(uint8_t *work);
190void protocore_dashboard_set(uint8_t *work);
191void protocore_dashboard_layout_json(uint8_t *work);
192void protocore_dashboard_values_json(uint8_t *work);
193void protocore_dashboard_on_control(uint8_t *work);
194void protocore_dashboard_parse_control(uint8_t *work);
195void protocore_dashboard_dispatch_control(uint8_t *work);
196void protocore_dashboard_begin(uint8_t *work);
197void protocore_dashboard_publish(uint8_t *work);
198
199// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
200// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
201// `Dashboard.configure(work)` resolves to a named function and becomes a DIRECT call. An extern table
202// leaves the call indirect and the symbol live at every level, -O2 -flto included.
203static const DashboardNs Dashboard __attribute__((unused)) = {
204 .configure = protocore_dashboard_configure,
205 .set = protocore_dashboard_set,
206 .layout_json = protocore_dashboard_layout_json,
207 .values_json = protocore_dashboard_values_json,
208 .on_control = protocore_dashboard_on_control,
209 .parse_control = protocore_dashboard_parse_control,
210 .dispatch_control = protocore_dashboard_dispatch_control,
211 .begin = protocore_dashboard_begin,
212 .publish = protocore_dashboard_publish,
213};
214
215/**
216 * @brief The PROTOCORE_DASHBOARD_BORROW bytes this module's state lives in.
217 *
218 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
219 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
220 * walks, so the state lasts the life of the program.
221 *
222 * @return the span.
223 */
224uint8_t *protocore_dashboard_span(void);
225
227
228#endif // PROTOCORE_ENABLE_DASHBOARD
229
230#endif // PROTOCORE_DASHBOARD_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97