DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
sparkplug.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 sparkplug.cpp
6 * @brief Sparkplug B payload + topic builder over the Protobuf codec (pure, host-tested).
7 */
8
10
11#if DETWS_ENABLE_SPARKPLUG
12
14#include <string.h>
15
16// Tahu Payload field numbers.
17#define SPB_PL_TIMESTAMP 1
18#define SPB_PL_METRICS 2
19#define SPB_PL_SEQ 3
20
21// Tahu Metric field numbers.
22#define SPB_MET_NAME 1
23#define SPB_MET_ALIAS 2
24#define SPB_MET_TIMESTAMP 3
25#define SPB_MET_DATATYPE 4
26#define SPB_MET_INT 10
27#define SPB_MET_LONG 11
28#define SPB_MET_FLOAT 12
29#define SPB_MET_DOUBLE 13
30#define SPB_MET_BOOL 14
31#define SPB_MET_STRING 15
32
33size_t spb_build_topic(char *buf, size_t cap, const char *group, const char *message_type, const char *edge_node,
34 const char *device)
35{
36 if (!buf || !group || !message_type || !edge_node)
37 return 0;
38 // spBv1.0/<group>/<message_type>/<edge_node>[/<device>]
39 size_t need = 8 /*"spBv1.0/"*/ + strnlen(group, cap) + 1 + strnlen(message_type, cap) + 1 + strnlen(edge_node, cap);
40 if (device)
41 need += 1 + strnlen(device, cap);
42 if (need + 1 > cap) // + NUL
43 return 0;
44 size_t p = 0;
45 const char *prefix = "spBv1.0/";
46 memcpy(buf + p, prefix, 8);
47 p += 8;
48 size_t n = strnlen(group, cap);
49 memcpy(buf + p, group, n);
50 p += n;
51 buf[p++] = '/';
52 n = strnlen(message_type, cap);
53 memcpy(buf + p, message_type, n);
54 p += n;
55 buf[p++] = '/';
56 n = strnlen(edge_node, cap);
57 memcpy(buf + p, edge_node, n);
58 p += n;
59 if (device)
60 {
61 buf[p++] = '/';
62 n = strnlen(device, cap);
63 memcpy(buf + p, device, n);
64 p += n;
65 }
66 buf[p] = '\0';
67 return p;
68}
69
70size_t spb_build_metric(uint8_t *buf, size_t cap, const SpbMetric *m)
71{
72 if (!buf || !m)
73 return 0;
74 PbWriter w;
75 pb_writer_init(&w, buf, cap);
76 if (m->name)
77 pb_string(&w, SPB_MET_NAME, m->name);
78 if (m->has_alias)
79 pb_uint64(&w, SPB_MET_ALIAS, m->alias);
80 if (m->has_timestamp)
81 pb_uint64(&w, SPB_MET_TIMESTAMP, m->timestamp);
82 pb_uint64(&w, SPB_MET_DATATYPE, m->datatype); // datatype is a uint32; the varint covers it
83 switch (m->kind)
84 {
85 case SpbMetricKind::SPB_M_INT:
86 pb_uint64(&w, SPB_MET_INT, m->int_value);
87 break;
88 case SpbMetricKind::SPB_M_LONG:
89 pb_uint64(&w, SPB_MET_LONG, m->long_value);
90 break;
91 case SpbMetricKind::SPB_M_FLOAT:
92 pb_float(&w, SPB_MET_FLOAT, m->float_value);
93 break;
94 case SpbMetricKind::SPB_M_DOUBLE:
95 pb_double(&w, SPB_MET_DOUBLE, m->double_value);
96 break;
97 case SpbMetricKind::SPB_M_BOOL:
98 pb_bool(&w, SPB_MET_BOOL, m->bool_value);
99 break;
100 case SpbMetricKind::SPB_M_STRING:
101 if (m->string_value)
102 pb_string(&w, SPB_MET_STRING, m->string_value);
103 break;
104 }
105 return pb_writer_finish(&w);
106}
107
108size_t spb_build_payload(uint8_t *buf, size_t cap, uint64_t timestamp, uint64_t seq, const SpbMetric *metrics, size_t n)
109{
110 if (!buf || (n && !metrics))
111 return 0;
112 PbWriter w;
113 pb_writer_init(&w, buf, cap);
114 pb_uint64(&w, SPB_PL_TIMESTAMP, timestamp);
115 for (size_t i = 0; i < n; i++)
116 {
117 // Serialize each Metric submessage into a bounded temp, then add it as a
118 // length-delimited field (Payload.metrics). A metric stays well under this bound
119 // unless it carries a large string, in which case the build fails closed.
120 uint8_t metric[DETWS_SPB_METRIC_MAX];
121 size_t mlen = spb_build_metric(metric, sizeof(metric), &metrics[i]);
122 if (!mlen)
123 return 0;
124 pb_bytes(&w, SPB_PL_METRICS, metric, mlen);
125 }
126 pb_uint64(&w, SPB_PL_SEQ, seq);
127 return pb_writer_finish(&w);
128}
129
130#endif // DETWS_ENABLE_SPARKPLUG
#define DETWS_SPB_METRIC_MAX
Max serialized size of one Sparkplug B metric submessage (stack temp, bytes).
Protocol Buffers wire codec (DETWS_ENABLE_PROTOBUF) - zero-heap streaming writer.
Sparkplug B payload + topic codec (DETWS_ENABLE_SPARKPLUG) - zero-heap builder for the Eclipse Sparkp...