17#if PC_ENABLE_DASHBOARD
28 const pc_widget *widgets =
nullptr;
31 pc_control_cb control_cb =
nullptr;
33static DashboardCtx s_dash;
35static const char *widget_type_name(pc_widget_type t)
39 case pc_widget_type::PC_WIDGET_GAUGE:
41 case pc_widget_type::PC_WIDGET_BAR:
43 case pc_widget_type::PC_WIDGET_SPARKLINE:
45 case pc_widget_type::PC_WIDGET_CHART:
47 case pc_widget_type::PC_WIDGET_BUTTON:
49 case pc_widget_type::PC_WIDGET_TOGGLE:
51 case pc_widget_type::PC_WIDGET_SLIDER:
58void pc_dashboard_configure(
const pc_widget *widgets, uint8_t count)
60 s_dash.widgets = widgets;
64 s_dash.values[i] = 0.0f;
68bool pc_dashboard_set(
const char *key,
float value)
70 if (!key || !s_dash.widgets)
74 for (uint8_t i = 0; i < s_dash.count; i++)
76 if (s_dash.widgets[i].key && strcmp(s_dash.widgets[i].key, key) == 0)
78 s_dash.values[i] = value;
90static const pc_field DASH_WIDGET[] = {
110static const pc_field DASH_VALUE[] = {
118int32_t pc_dashboard_layout_json(
char *out, uint32_t cap)
120 if (!out || cap == 0)
133 for (uint8_t i = 0; i < s_dash.count; i++)
135 const pc_widget *w = &s_dash.widgets[i];
136 if (
pc_frame_append(out, cap, DASH_WIDGET, i ?
"," :
"", widget_type_name(w->type), w->label ? w->label :
"",
137 w->key ? w->key :
"", (double)w->min, (double)w->max, w->unit ? w->unit :
"") == 0)
145int32_t pc_dashboard_values_json(
char *out, uint32_t cap)
147 if (!out || cap == 0)
160 for (uint8_t i = 0; i < s_dash.count; i++)
162 if (
pc_frame_append(out, cap, DASH_VALUE, i ?
"," :
"", s_dash.widgets[i].key ? s_dash.widgets[i].key :
"",
163 (double)s_dash.values[i]) == 0)
175void pc_dashboard_on_control(pc_control_cb cb)
177 s_dash.control_cb = cb;
183static const char *control_value_ptr(
const char *s,
const char *key)
186 pc_sb sb_pat = {pat,
sizeof(pat), 0,
true};
194 const char *p = strstr(s, pat);
199 p += strnlen(pat,
sizeof(pat));
200 while (*p ==
' ' || *p ==
'\t')
209 while (*p ==
' ' || *p ==
'\t')
216bool pc_dashboard_parse_control(
const char *msg,
char *key_out,
size_t key_cap,
float *value_out)
218 if (!msg || !key_out || key_cap == 0 || !value_out)
223 const char *kp = control_value_ptr(msg,
"k");
224 const char *vp = control_value_ptr(msg,
"v");
225 if (!kp || !vp || *kp !=
'"')
231 while (*kp && *kp !=
'"' && i + 1 < key_cap)
233 key_out[i++] = *kp++;
241 const char *end =
nullptr;
251bool pc_dashboard_dispatch_control(
const char *msg)
255 if (!pc_dashboard_parse_control(msg, key,
sizeof(key), &value))
259 if (s_dash.control_cb)
261 s_dash.control_cb(key, value);
263 return s_dash.control_cb !=
nullptr;
Real-time SVG telemetry dashboard (PC_ENABLE_DASHBOARD).
size_t pc_frame_append(char *out, size_t cap, const pc_field *spec,...)
Append spec to the NUL-terminated contents already in out.
Declarative frame builder: a frame is a static table of typed fields, built by one engine.
@ PC_FK_G
double, printf %.<width>g (width 0 means 6)
@ PC_FK_LIT
literal text from lit; takes no argument
Tiny no-stdlib base-10 number parsers (strtol/strtoul/strtof replacements).
float pc_strtof(const char *s, const char **end)
Parse a float (integer[.frac][e[+/-]exp]); sets end (or to s if none).
#define PC_DASHBOARD_MAX_WIDGETS
Maximum widgets in the dashboard table (BSS value array).
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.
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.
One field of a frame. Frames are static const pc_field[], so they live in rodata.
Bump-append target; ok latches false once an append would overflow cap.