ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
senml.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 senml.h
6 * @brief Sensor Measurement Lists (SenML, RFC 8428): the Pack builders and the Record resolver.
7 *
8 * RFC 8428 sec 3 names the units: a SenML Record is "one measurement or configuration instance in
9 * time presented using the SenML data model", and a SenML Pack is "one or more SenML Records in an
10 * array structure". This module builds a Pack from a caller-owned Record array and resolves a Pack
11 * in place of a consumer.
12 *
13 * A Record carries Base Fields (RFC 8428 sec 4.1) that apply to it and the Records after it, and
14 * Regular Fields (sec 4.2) that apply to it alone. RFC 8428 sec 5.1.1:
15 * @code
16 * [
17 * {"n":"urn:dev:ow:10e2073a01080063","u":"Cel","v":23.1}
18 * ]
19 * @endcode
20 *
21 * The JSON representation (sec 5, application/senml+json) writes the labels as member names. The
22 * CBOR representation (sec 6, application/senml+cbor) writes the Table 4 integer map keys instead;
23 * that table is conclusive, so one walk feeds every binary encoding through @ref protocore_codec and
24 * the encoding is an argument. A Number that is integral is emitted as an integer, so a Time keeps
25 * full precision; otherwise it is emitted as a floating-point value.
26 *
27 * Both builders write into a caller buffer and report 0 bytes when it will not hold the whole Pack.
28 * The resolver (sec 4.6) folds the Base Name and Base Time into each Record, so each output Record
29 * carries a full Name and an absolute Time and stands alone.
30 *
31 * The module exports one symbol, @ref Senml. Everything in senml.c has internal linkage.
32 *
33 * @author Douglas Quigg (dstroy0)
34 * @date 2026
35 */
36
37#ifndef PROTOCORE_SENML_H
38#define PROTOCORE_SENML_H
39
40#include "protocore_config.h" // the entry point: protocore_types.h for the widths
41
42#if PROTOCORE_ENABLE_SENML
43
44#include "network_drivers/presentation/codec/codec.h" // protocore_codec: the binary encoding is an argument
45
47
48/** @brief Longest resolved Name (Base Name concatenated with Name), the NUL included. */
49#define PROTOCORE_SENML_RESOLVED_NAME_MAX 96
50
51/** @brief Which value field a Record carries (RFC 8428 sec 4.2). */
52typedef enum PROTO_ENUM_PACKED
53{
54 SENML_VALUE_NONE, ///< no value field, as in a Base-Fields-only Record
55 SENML_VALUE_NUMBER, ///< Value (v), a Number; emitted as an integer when integral
56 SENML_VALUE_STRING, ///< String Value (vs)
57 SENML_VALUE_BOOLEAN, ///< Boolean Value (vb)
58} SenmlValueKind;
59
60/**
61 * @brief One SenML Record (RFC 8428 sec 3): its Base Fields (sec 4.1) and Regular Fields (sec 4.2).
62 *
63 * Every string is borrowed, not copied, and a null one leaves its label out of the Pack.
64 */
65typedef struct
66{
67 const char *base_name; ///< Base Name (bn), prepended to the Names that follow
68 proto_bool has_base_time; ///< a Base Time is present
69 double base_time; ///< Base Time (bt), added to the Times that follow
70 const char *name; ///< Name (n)
71 const char *unit; ///< Unit (u)
72 SenmlValueKind value_kind; ///< which of the three value fields below this Record carries
73 double value; ///< Value (v)
74 const char *string_value; ///< String Value (vs)
75 proto_bool boolean_value; ///< Boolean Value (vb)
76 proto_bool has_time; ///< a Time is present
77 double time; ///< Time (t)
78} SenmlRecord;
79
80/**
81 * @brief One resolved SenML Record (RFC 8428 sec 4.6): no Base Fields left and no relative Time.
82 */
83typedef struct
84{
85 char name[PROTOCORE_SENML_RESOLVED_NAME_MAX]; ///< Name (n): the Base Name concatenated with the Name
86 const char *unit; ///< Unit (u), borrowed from the input Record
87 SenmlValueKind value_kind; ///< which value field this Record carries
88 double value; ///< Value (v)
89 const char *string_value; ///< String Value (vs)
90 proto_bool boolean_value; ///< Boolean Value (vb)
91 proto_bool has_time; ///< a Time is present
92 double time; ///< Time (t): the Base Time added to the Time
93} SenmlResolved;
94
95/** @brief RFC 8428 sec 3: the Pack a call reads, one array of Records. */
96typedef struct
97{
98 const SenmlRecord *records; ///< the Records the array holds
99 size_t count; ///< how many of them
100} SenmlPackArgs;
101
102/** @brief RFC 8428 sec 5: where the JSON representation (application/senml+json) lands. */
103typedef struct
104{
105 char *buf; ///< the buffer the text Pack is written into
106 size_t cap; ///< how much room it has, the NUL included
107} SenmlJsonArgs;
108
109/** @brief RFC 8428 sec 6: the binary encoding a Pack takes, and where it lands. */
110typedef struct
111{
112 const protocore_codec *codec; ///< the encoding the Table 4 integer labels are written through
113 uint8_t *buf; ///< the buffer the encoded Pack is written into
114 size_t cap; ///< how much room it has
115} SenmlBinaryArgs;
116
117/** @brief RFC 8428 sec 4.6: where the resolved Records land. */
118typedef struct
119{
120 SenmlResolved *out; ///< the array a resolve fills
121 size_t max; ///< how many Records that array holds
122} SenmlResolvedArgs;
123
124/**
125 * @brief The SenML Pack builders and the Record resolver.
126 *
127 * A caller sets the members a call takes, invokes it through ::Senml, and reads the outcome off the
128 * same handle.
129 *
130 * No slot member: a Pack is the whole unit every call names, so no call names a row.
131 *
132 * @var SenmlNs::pack the Records a build encodes or a resolve reads (RFC 8428 sec 3)
133 * @var SenmlNs::json where the JSON representation lands (RFC 8428 sec 5)
134 * @var SenmlNs::binary the encoding a Pack takes and where it lands (RFC 8428 sec 6)
135 * @var SenmlNs::resolved where the resolved Records land (RFC 8428 sec 4.6)
136 * @var SenmlNs::ok a call's true/false outcome
137 * @var SenmlNs::n the bytes a build wrote, excluding the NUL, or the Records a resolve
138 * produced; 0 when the call failed
139 * @var SenmlNs::json_build encode @c pack into @c json as application/senml+json (sec 5)
140 * @var SenmlNs::binary_build encode @c pack into @c binary through its codec, with the sec 6
141 * Table 4 integer labels
142 * @var SenmlNs::resolve carry the Base Name and Base Time across @c pack and fold them into
143 * each Record, into @c resolved (sec 4.6)
144 */
145typedef struct
146{
147 SenmlPackArgs pack; ///< what a call reads
148 SenmlJsonArgs json; ///< where the text Pack lands
149 SenmlBinaryArgs binary; ///< which encoding, and where the encoded Pack lands
150 SenmlResolvedArgs resolved; ///< where the resolved Records land
151 proto_bool ok;
152 size_t n;
153} SenmlVars;
154
155/** @brief The operands and the outcome. */
156extern SenmlVars SenmlV;
157
158/** @brief The entries. */
159typedef struct
160{
161 void (*const json_build)(uint8_t *work);
162 void (*const binary_build)(uint8_t *work);
163 void (*const resolve)(uint8_t *work);
164} SenmlNs;
165
166// What the table binds, defined once in the .c and taking one parameter each: everything
167// else an entry needs is an operand in SenmlV or a region of the borrow at a fixed offset.
168void protocore_senml_json_build(uint8_t *work);
169void protocore_senml_binary_build(uint8_t *work);
170void protocore_senml_resolve(uint8_t *work);
171
172// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
173// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
174// `Senml.json_build(work)` resolves to a named function and becomes a DIRECT call. An extern table
175// leaves the call indirect and the symbol live at every level, -O2 -flto included.
176static const SenmlNs Senml __attribute__((unused)) = {
177 .json_build = protocore_senml_json_build,
178 .binary_build = protocore_senml_binary_build,
179 .resolve = protocore_senml_resolve,
180};
181
183
184#endif // PROTOCORE_ENABLE_SENML
185
186#endif // PROTOCORE_SENML_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
One binary codec interface; a wire encoding is an instance of it.
A wire encoding: the ten writes, the peek, and the nine reads.
Definition codec.h:60
#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