Sparkplug B C++ Library 1.0.0
Modern C++-23 implementation of Eclipse Sparkplug B 2.2 specification
Loading...
Searching...
No Matches
payload_builder.hpp
1// include/sparkplug/payload_builder.hpp
2#pragma once
3
4#include "datatype.hpp"
5#include "sparkplug_b.pb.h"
6
7#include <chrono>
8#include <concepts>
9#include <cstdint>
10#include <optional>
11#include <string>
12#include <string_view>
13#include <type_traits>
14#include <utility>
15#include <vector>
16
17namespace sparkplug {
18
19// Concepts for Sparkplug B type system
20
22template <typename T>
23concept SparkplugSignedInteger = std::same_as<std::remove_cvref_t<T>, int8_t> ||
24 std::same_as<std::remove_cvref_t<T>, int16_t> ||
25 std::same_as<std::remove_cvref_t<T>, int32_t> ||
26 std::same_as<std::remove_cvref_t<T>, int64_t>;
27
29template <typename T>
30concept SparkplugUnsignedInteger = std::same_as<std::remove_cvref_t<T>, uint8_t> ||
31 std::same_as<std::remove_cvref_t<T>, uint16_t> ||
32 std::same_as<std::remove_cvref_t<T>, uint32_t> ||
33 std::same_as<std::remove_cvref_t<T>, uint64_t>;
34
36template <typename T>
38
40template <typename T>
41concept SparkplugFloat = std::same_as<std::remove_cvref_t<T>, float> ||
42 std::same_as<std::remove_cvref_t<T>, double>;
43
45template <typename T>
46concept SparkplugBoolean = std::same_as<std::remove_cvref_t<T>, bool>;
47
49template <typename T>
50concept SparkplugString = std::convertible_to<T, std::string_view> &&
52
54template <typename T>
56
58template <typename T>
61
62namespace detail {
63
64template <SparkplugMetricType T>
65consteval DataType get_datatype() noexcept {
66 using BaseT = std::remove_cvref_t<T>;
67 if constexpr (std::is_same_v<BaseT, int8_t>)
68 return DataType::Int8;
69 else if constexpr (std::is_same_v<BaseT, int16_t>)
70 return DataType::Int16;
71 else if constexpr (std::is_same_v<BaseT, int32_t>)
72 return DataType::Int32;
73 else if constexpr (std::is_same_v<BaseT, int64_t>)
74 return DataType::Int64;
75 else if constexpr (std::is_same_v<BaseT, uint8_t>)
76 return DataType::UInt8;
77 else if constexpr (std::is_same_v<BaseT, uint16_t>)
78 return DataType::UInt16;
79 else if constexpr (std::is_same_v<BaseT, uint32_t>)
80 return DataType::UInt32;
81 else if constexpr (std::is_same_v<BaseT, uint64_t>)
82 return DataType::UInt64;
83 else if constexpr (std::is_same_v<BaseT, float>)
84 return DataType::Float;
85 else if constexpr (std::is_same_v<BaseT, double>)
86 return DataType::Double;
87 else if constexpr (std::is_same_v<BaseT, bool>)
88 return DataType::Boolean;
89 else
90 return DataType::String;
91}
92
93template <SparkplugMetricType T>
94void set_metric_value(org::eclipse::tahu::protobuf::Payload::Metric* metric, T&& value) {
95 using BaseT = std::remove_cvref_t<T>;
96
97 if constexpr (std::is_same_v<BaseT, int8_t> || std::is_same_v<BaseT, int16_t> ||
98 std::is_same_v<BaseT, int32_t> || std::is_same_v<BaseT, uint8_t> ||
99 std::is_same_v<BaseT, uint16_t> || std::is_same_v<BaseT, uint32_t>) {
100 metric->set_int_value(value);
101 } else if constexpr (std::is_same_v<BaseT, int64_t> ||
102 std::is_same_v<BaseT, uint64_t>) {
103 metric->set_long_value(value);
104 } else if constexpr (std::is_same_v<BaseT, float>) {
105 metric->set_float_value(value);
106 } else if constexpr (std::is_same_v<BaseT, double>) {
107 metric->set_double_value(value);
108 } else if constexpr (std::is_same_v<BaseT, bool>) {
109 metric->set_boolean_value(value);
110 } else {
111 // Handle all string-like types
112 metric->set_string_value(std::string(value));
113 }
114}
115
116template <SparkplugMetricType T>
117void add_metric_to_payload(org::eclipse::tahu::protobuf::Payload& payload,
118 std::string_view name,
119 T&& value,
120 std::optional<uint64_t> alias,
121 std::optional<uint64_t> timestamp_ms) {
122 auto* metric = payload.add_metrics();
123
124 if (!name.empty()) {
125 metric->set_name(std::string(name));
126 }
127 if (alias.has_value()) {
128 metric->set_alias(*alias);
129 }
130
131 metric->set_datatype(std::to_underlying(get_datatype<T>()));
132 set_metric_value(metric, std::forward<T>(value));
133
134 // Use provided timestamp or current time
135 uint64_t ts;
136 if (timestamp_ms.has_value()) {
137 ts = *timestamp_ms;
138 } else {
139 auto now = std::chrono::system_clock::now();
140 ts = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch())
141 .count();
142 }
143 metric->set_timestamp(ts);
144}
145
146} // namespace detail
147
188public:
193
205 template <SparkplugMetricType T>
206 PayloadBuilder& add_metric(std::string_view name, T&& value) {
207 detail::add_metric_to_payload(payload_, name, std::forward<T>(value), std::nullopt,
208 std::nullopt);
209 return *this;
210 }
211
224 template <SparkplugMetricType T>
225 PayloadBuilder& add_metric(std::string_view name, T&& value, uint64_t timestamp_ms) {
226 detail::add_metric_to_payload(payload_, name, std::forward<T>(value), std::nullopt,
227 timestamp_ms);
228 return *this;
229 }
230
243 template <SparkplugMetricType T>
245 add_metric_with_alias(std::string_view name, uint64_t alias, T&& value) {
246 detail::add_metric_to_payload(payload_, name, std::forward<T>(value), alias,
247 std::nullopt);
248 return *this;
249 }
250
265 template <SparkplugMetricType T>
266 PayloadBuilder& add_metric_with_alias(std::string_view name,
267 uint64_t alias,
268 T&& value,
269 uint64_t timestamp_ms) {
270 detail::add_metric_to_payload(payload_, name, std::forward<T>(value), alias,
271 timestamp_ms);
272 return *this;
273 }
274
289 template <SparkplugMetricType T>
290 PayloadBuilder& add_metric_by_alias(uint64_t alias, T&& value) {
291 detail::add_metric_to_payload(payload_, "", std::forward<T>(value), alias,
292 std::nullopt);
293 return *this;
294 }
295
308 template <SparkplugMetricType T>
309 PayloadBuilder& add_metric_by_alias(uint64_t alias, T&& value, uint64_t timestamp_ms) {
310 detail::add_metric_to_payload(payload_, "", std::forward<T>(value), alias,
311 timestamp_ms);
312 return *this;
313 }
314
325 payload_.set_timestamp(ts);
326 timestamp_explicitly_set_ = true;
327 return *this;
328 }
329
339 PayloadBuilder& set_seq(uint64_t seq) {
340 payload_.set_seq(seq);
341 seq_explicitly_set_ = true;
342 return *this;
343 }
344
345 // Add Node Control metrics (convenience methods for NBIRTH)
346 PayloadBuilder& add_node_control_rebirth(bool value = false) {
347 add_metric("Node Control/Rebirth", value);
348 return *this;
349 }
350
351 PayloadBuilder& add_node_control_reboot(bool value = false) {
352 add_metric("Node Control/Reboot", value);
353 return *this;
354 }
355
356 PayloadBuilder& add_node_control_next_server(bool value = false) {
357 add_metric("Node Control/Next Server", value);
358 return *this;
359 }
360
361 PayloadBuilder& add_node_control_scan_rate(int64_t value) {
362 add_metric("Node Control/Scan Rate", value);
363 return *this;
364 }
365
366 // Query methods
367 [[nodiscard]] bool has_seq() const noexcept {
368 return seq_explicitly_set_;
369 }
370 [[nodiscard]] bool has_timestamp() const noexcept {
371 return timestamp_explicitly_set_;
372 }
373
374 // Build and access
375 [[nodiscard]] std::vector<uint8_t> build() const;
376 [[nodiscard]] const org::eclipse::tahu::protobuf::Payload& payload() const noexcept;
377 [[nodiscard]] org::eclipse::tahu::protobuf::Payload& mutable_payload() noexcept {
378 return payload_;
379 }
380
381private:
382 org::eclipse::tahu::protobuf::Payload payload_;
383 bool seq_explicitly_set_{false};
384 bool timestamp_explicitly_set_{false};
385};
386
387} // namespace sparkplug
Type-safe builder for Sparkplug B payloads with automatic type detection.
PayloadBuilder & add_metric_by_alias(uint64_t alias, T &&value)
Adds a metric by alias only (for NDATA messages).
PayloadBuilder & set_seq(uint64_t seq)
Sets the sequence number manually.
PayloadBuilder & add_metric(std::string_view name, T &&value, uint64_t timestamp_ms)
Adds a metric by name with a custom timestamp.
PayloadBuilder & add_metric(std::string_view name, T &&value)
Adds a metric by name only (for NBIRTH without aliases).
PayloadBuilder & add_metric_by_alias(uint64_t alias, T &&value, uint64_t timestamp_ms)
Adds a metric by alias with a custom timestamp.
PayloadBuilder & add_metric_with_alias(std::string_view name, uint64_t alias, T &&value)
Adds a metric with both name and alias (for NBIRTH messages).
PayloadBuilder & set_timestamp(uint64_t ts)
Sets the payload-level timestamp.
PayloadBuilder()
Constructs an empty payload.
PayloadBuilder & add_metric_with_alias(std::string_view name, uint64_t alias, T &&value, uint64_t timestamp_ms)
Adds a metric with name, alias, and custom timestamp (for NBIRTH with historical data).
Floating-point types supported by Sparkplug B.
Any integer type supported by Sparkplug B (signed or unsigned)
Any metric type supported by Sparkplug B.
Numeric types (integers and floats)
Signed integer types supported by Sparkplug B.
String-like types supported by Sparkplug B.
Unsigned integer types supported by Sparkplug B.