DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
senml.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 senml.cpp
6 * @brief SenML-JSON + SenML-CBOR pack builders (pure, host-tested).
7 */
8
10
11#if DETWS_ENABLE_SENML
12
15#include <stdio.h> // snprintf for the JSON number formatting
16
17// SenML-CBOR integer labels (RFC 8428).
18#define SENML_LBL_BN (-2)
19#define SENML_LBL_BT (-3)
20#define SENML_LBL_N 0
21#define SENML_LBL_U 1
22#define SENML_LBL_V 2
23#define SENML_LBL_VS 3
24#define SENML_LBL_VB 4
25#define SENML_LBL_T 6
26
27// True when @p d is an integer that fits in int64 (so it can be emitted losslessly as one).
28static bool is_integral(double d)
29{
30 return d >= -9.2e18 && d <= 9.2e18 && d == (double)(int64_t)d;
31}
32
33// Emit a SenML number into a JSON value position: an integer when integral (keeps timestamp
34// precision), otherwise a %g float.
35static void json_num(JsonWriter &w, double d)
36{
37 char tmp[32];
38 if (is_integral(d))
39 snprintf(tmp, sizeof(tmp), "%lld", (long long)d);
40 else
41 snprintf(tmp, sizeof(tmp), "%g", d);
42 w.raw(tmp);
43}
44
45size_t senml_json_build(char *buf, size_t cap, const SenmlRecord *records, size_t count)
46{
47 if (!buf || (count && !records))
48 return 0;
49 JsonWriter w(buf, cap);
50 w.begin_array();
51 for (size_t i = 0; i < count; i++)
52 {
53 const SenmlRecord &r = records[i];
54 w.begin_object();
55 if (r.base_name)
56 w.kv_str("bn", r.base_name);
57 if (r.has_base_time)
58 {
59 w.key("bt");
60 json_num(w, r.base_time);
61 }
62 if (r.name)
63 w.kv_str("n", r.name);
64 if (r.unit)
65 w.kv_str("u", r.unit);
66 switch (r.value_kind)
67 {
68 case SenmlValueKind::SENML_V_FLOAT:
69 w.key("v");
70 json_num(w, r.value);
71 break;
72 case SenmlValueKind::SENML_V_STRING:
73 if (r.value_str)
74 w.kv_str("vs", r.value_str);
75 break;
76 case SenmlValueKind::SENML_V_BOOL:
77 w.kv_bool("vb", r.value_bool);
78 break;
79 case SenmlValueKind::SENML_V_NONE:
80 break;
81 }
82 if (r.has_time)
83 {
84 w.key("t");
85 json_num(w, r.time);
86 }
87 w.end_object();
88 }
89 w.end_array();
90 return w.ok() ? w.length() : 0;
91}
92
93// Emit a SenML number into a CBOR value: an integer when integral, else a float.
94static void cbor_num(CborWriter *w, double d)
95{
96 if (is_integral(d))
97 cbor_int(w, (int64_t)d);
98 else
99 cbor_float(w, (float)d);
100}
101
102static size_t record_fields(const SenmlRecord &r)
103{
104 size_t n = 0;
105 if (r.base_name)
106 n++;
107 if (r.has_base_time)
108 n++;
109 if (r.name)
110 n++;
111 if (r.unit)
112 n++;
113 if (r.value_kind != SenmlValueKind::SENML_V_NONE &&
114 !(r.value_kind == SenmlValueKind::SENML_V_STRING && !r.value_str))
115 n++;
116 if (r.has_time)
117 n++;
118 return n;
119}
120
121size_t senml_cbor_build(uint8_t *buf, size_t cap, const SenmlRecord *records, size_t count)
122{
123 if (!buf || (count && !records))
124 return 0;
125 CborWriter w;
126 cbor_init(&w, buf, cap);
127 cbor_array(&w, count);
128 for (size_t i = 0; i < count; i++)
129 {
130 const SenmlRecord &r = records[i];
131 cbor_map(&w, record_fields(r));
132 if (r.base_name)
133 {
134 cbor_int(&w, SENML_LBL_BN);
135 cbor_text(&w, r.base_name);
136 }
137 if (r.has_base_time)
138 {
139 cbor_int(&w, SENML_LBL_BT);
140 cbor_num(&w, r.base_time);
141 }
142 if (r.name)
143 {
144 cbor_int(&w, SENML_LBL_N);
145 cbor_text(&w, r.name);
146 }
147 if (r.unit)
148 {
149 cbor_int(&w, SENML_LBL_U);
150 cbor_text(&w, r.unit);
151 }
152 switch (r.value_kind)
153 {
154 case SenmlValueKind::SENML_V_FLOAT:
155 cbor_int(&w, SENML_LBL_V);
156 cbor_num(&w, r.value);
157 break;
158 case SenmlValueKind::SENML_V_STRING:
159 if (r.value_str)
160 {
161 cbor_int(&w, SENML_LBL_VS);
162 cbor_text(&w, r.value_str);
163 }
164 break;
165 case SenmlValueKind::SENML_V_BOOL:
166 cbor_int(&w, SENML_LBL_VB);
167 cbor_bool(&w, r.value_bool);
168 break;
169 case SenmlValueKind::SENML_V_NONE:
170 break;
171 }
172 if (r.has_time)
173 {
174 cbor_int(&w, SENML_LBL_T);
175 cbor_num(&w, r.time);
176 }
177 }
178 return cbor_ok(&w) ? cbor_len(&w) : 0;
179}
180
181#endif // DETWS_ENABLE_SENML
Layer 6 (Presentation) - zero-heap CBOR (RFC 8949) encoder.
Builds a JSON document into a fixed caller buffer, no heap.
Definition json.h:59
void begin_object()
Open { (as a value/element where applicable).
Definition json.cpp:144
void key(const char *k)
Emit an object member name ("k":); follow with one value.
Definition json.cpp:161
void raw(const char *literal)
Emit a pre-formatted literal verbatim.
Definition json.cpp:207
void kv_bool(const char *k, bool v)
"k":true|false.
Definition json.cpp:228
void end_array()
Close ].
Definition json.cpp:156
void kv_str(const char *k, const char *v)
"k":"v" (escaped).
Definition json.cpp:213
bool ok() const
< False after any overflow / structural error.
Definition json.h:90
void end_object()
Close }.
Definition json.cpp:148
size_t length() const
< Bytes written so far (excludes the NUL).
Definition json.h:94
void begin_array()
Open [.
Definition json.cpp:152
Layer 6 (Presentation) - zero-heap JSON: a bounded writer and top-level reader.
SenML (RFC 8428) sensor-measurement pack builder (DETWS_ENABLE_SENML) - zero-heap SenML-JSON and SenM...