ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
senml.h
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.h
6 * @brief SenML (RFC 8428) sensor-measurement pack builder (PC_ENABLE_SENML) - zero-heap
7 * SenML-JSON and SenML-CBOR encoders over the shipped JSON / CBOR codecs. SenML is
8 * the standard measurement format for CoAP / LwM2M / HTTP telemetry.
9 *
10 * A SenML pack is an array of records. Each record carries an optional base name / base time
11 * (applied to the records that follow), a name, a unit, and exactly one value (a number, a
12 * string, or a boolean), plus an optional time:
13 * @code
14 * [{"bn":"urn:dev:ow:10e2073a01080063","u":"Cel","v":23.1}]
15 * @endcode
16 * SenML-JSON uses the text labels (bn/bt/n/u/v/vs/vb/t); SenML-CBOR uses the integer labels
17 * (n=0 u=1 v=2 vs=3 vb=4 t=6, bn=-2 bt=-3) in a CBOR map per record. Numbers that are
18 * integral are emitted as integers (so timestamps keep full precision); otherwise as floats.
19 *
20 * The caller fills a @ref SenmlRecord array and the builder emits the whole pack into a
21 * caller buffer (fail-closed on overflow). Verified against the RFC 8428 example. A resolver
22 * (@ref pc_senml_resolve, RFC 8428 §4.6) folds the base name / base time into each record so a
23 * consumer gets standalone records with a full name and an absolute time.
24 *
25 * @author Douglas Quigg (dstroy0)
26 * @date 2026
27 */
28
29#ifndef PROTOCORE_SENML_H
30#define PROTOCORE_SENML_H
31
32#include "protocore_config.h"
33
34#if PC_ENABLE_SENML
35
36#include <stddef.h>
37#include <stdint.h>
38
39/** @brief Which value field a record carries. */
40enum class SenmlValueKind : uint8_t
41{
42 SENML_V_NONE, ///< no value (e.g. a base-only record)
43 SENML_V_FLOAT, ///< numeric value (v) - emitted as int when integral, else float
44 SENML_V_STRING, ///< string value (vs)
45 SENML_V_BOOL ///< boolean value (vb)
46};
47
48/** @brief One SenML record. String pointers are borrowed (not copied); nullptr fields are skipped. */
49struct SenmlRecord
50{
51 const char *base_name; ///< bn (optional)
52 bool has_base_time;
53 double base_time; ///< bt (optional)
54 const char *name; ///< n (optional)
55 const char *unit; ///< u (optional)
56 SenmlValueKind value_kind;
57 double value; ///< v (when value_kind == SenmlValueKind::SENML_V_FLOAT)
58 const char *value_str; ///< vs (when value_kind == SenmlValueKind::SENML_V_STRING)
59 bool value_bool; ///< vb (when value_kind == SenmlValueKind::SENML_V_BOOL)
60 bool has_time;
61 double time; ///< t (optional)
62};
63
64/** @brief Build a SenML-JSON pack. Returns bytes written (excluding NUL), or 0 on overflow. */
65size_t pc_senml_json_build(char *buf, size_t cap, const SenmlRecord *records, size_t count);
66
67/** @brief Build a SenML-CBOR pack (a CBOR array of integer-keyed maps). Returns bytes, or 0. */
68size_t pc_senml_cbor_build(uint8_t *buf, size_t cap, const SenmlRecord *records, size_t count);
69
70// --- resolution (RFC 8428 §4.6): apply the base fields to produce standalone records ---
71
72/** @brief Maximum resolved-name length (base name + name), including the NUL. */
73#define SENML_RESOLVED_NAME_MAX 96
74
75/** @brief A resolved SenML record: the base name / time folded in, so it stands alone. */
76struct SenmlResolved
77{
78 char name[SENML_RESOLVED_NAME_MAX]; ///< the base name concatenated with the record name
79 const char *unit; ///< u (borrowed from the input; may be null)
80 SenmlValueKind value_kind;
81 double value;
82 const char *value_str;
83 bool value_bool;
84 bool has_time;
85 double time; ///< the base time added to the record time (an absolute time)
86};
87
88/**
89 * @brief Resolve a SenML pack (RFC 8428 §4.6): carry the base name / base time forward across records so
90 * each output record's name is the full base+name and its time is the absolute base+time.
91 *
92 * A record's base name / base time becomes active for that record and every record after it, until a later
93 * record overrides it. Each input record produces one resolved record (a base-only carrier resolves to a
94 * value-less record the caller can skip). @return the number of records resolved (min of @p n and @p max).
95 */
96size_t pc_senml_resolve(const SenmlRecord *in, size_t n, SenmlResolved *out, size_t max);
97
98#endif // PC_ENABLE_SENML
99
100#endif // PROTOCORE_SENML_H
User-facing configuration for ProtoCore.