DeterministicESPAsyncWebServer v6.28.0
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
mtconnect.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 mtconnect.h
6 * @brief MTConnect agent response codec (DETWS_ENABLE_MTCONNECT).
7 *
8 * MTConnect (ANSI/MTC1.4) is the manufacturing-equipment read standard: an HTTP agent answers `probe`,
9 * `current`, `sample`, and `asset` requests with XML documents. This builds the two most-used response
10 * documents into a caller buffer, so the web server is an MTConnect agent over the existing HTTP stack:
11 *
12 * - **MTConnectStreams** (the `current` / `sample` response): a header carrying the agent
13 * instanceId + nextSequence, then per-DataItem `<Samples>/<Events>/<Condition>` values.
14 * - **MTConnectDevices** (the `probe` response): the device model - a `<Device>` with its
15 * `<DataItems>` (each `category`/`id`/`type`, optional `name`/`units`) - that a client
16 * discovers before it streams.
17 * - **MTConnectAssets** (the `asset` response): the tool/fixture inventory - a `<CuttingTool>`
18 * with its `<CuttingToolLifeCycle>` (`<ToolLife>` remaining minutes / part count) - that a
19 * client reads out of band from the observation stream.
20 * - **MTConnectError** (a request error): the header + an `<Errors><Error errorCode=..>` element.
21 *
22 * A streams document is assembled incrementally: open it, add each observation, close it. The instanceId
23 * (an agent-boot id) + a monotonically increasing sequence number give a subscriber the from/count
24 * long-poll semantics. Pure text framing, zero heap, no stdlib, host-testable; values are XML-escaped.
25 */
26
27#ifndef DETERMINISTICESPASYNCWEBSERVER_MTCONNECT_H
28#define DETERMINISTICESPASYNCWEBSERVER_MTCONNECT_H
29
30#include "ServerConfig.h"
31#include <stddef.h>
32#include <stdint.h>
33
34#if DETWS_ENABLE_MTCONNECT
35
36/** @brief The MTConnect DataItem category (which stream element wraps the value). */
37enum class DetwsMtcCategory : uint8_t
38{
39 DETWS_MTC_SAMPLE, ///< a measured value (<Samples>).
40 DETWS_MTC_EVENT, ///< a discrete state (<Events>).
41 DETWS_MTC_CONDITION ///< a condition (<Condition>): value is the sub-element name (Normal/Warning/Fault).
42};
43
44/** @brief Incremental MTConnectStreams builder over a caller buffer. */
45struct DetwsMtcStreams
46{
47 char *buf;
48 size_t cap;
49 size_t len; ///< bytes written so far (excl NUL).
50 bool ok; ///< cleared on any overflow; the final length is 0 when not ok.
51 bool in_comp; ///< a <ComponentStream> is open.
52};
53
54/**
55 * @brief Begin an MTConnectStreams document: XML declaration + header + open <Streams>.
56 * @param instance_id agent instanceId (boot id).
57 * @param next_seq the nextSequence the agent will assign.
58 * @param device_name the single device's name/uuid for the ComponentStream.
59 */
60void detws_mtc_streams_begin(DetwsMtcStreams *s, char *buf, size_t cap, uint64_t instance_id, uint64_t next_seq,
61 const char *device_name);
62
63/**
64 * @brief Add one observation.
65 * @param cat the DataItem category.
66 * @param type the DataItem type element name (e.g. "Position", "Execution", "Availability").
67 * @param data_id the DataItem id attribute.
68 * @param seq this observation's sequence number.
69 * @param timestamp ISO-8601 timestamp string.
70 * @param value the value text (XML-escaped); for a CONDITION it is the sub-element (Normal/Fault/...).
71 */
72void detws_mtc_streams_add(DetwsMtcStreams *s, DetwsMtcCategory cat, const char *type, const char *data_id,
73 uint64_t seq, const char *timestamp, const char *value);
74
75/** @brief Finish the document (close any open component + <Streams> + root). @return length, or 0 on overflow. */
76size_t detws_mtc_streams_end(DetwsMtcStreams *s);
77
78/**
79 * @brief Build a complete MTConnectError document.
80 * @return length written, or 0 on overflow.
81 */
82size_t detws_mtc_error(uint64_t instance_id, const char *error_code, const char *message, char *out, size_t cap);
83
84/**
85 * @brief Begin an MTConnectDevices (`probe`) document: XML declaration + header + open one `<Device>`.
86 * @param instance_id agent instanceId (boot id).
87 * @param device_id the Device `id` attribute.
88 * @param device_name the Device `name` attribute.
89 * @param uuid the Device `uuid` attribute.
90 *
91 * Reuses ::DetwsMtcStreams as the incremental buffer builder (as ::detws_mtc_error does).
92 */
93void detws_mtc_devices_begin(DetwsMtcStreams *s, char *buf, size_t cap, uint64_t instance_id, const char *device_id,
94 const char *device_name, const char *uuid);
95
96/**
97 * @brief Add one `<DataItem>` to the device model.
98 * @param cat the DataItem category (SAMPLE / EVENT / CONDITION).
99 * @param id the DataItem `id` attribute.
100 * @param type the DataItem `type` attribute (e.g. "Position", "Execution", "Availability").
101 * @param name optional `name` attribute (omitted when null/empty).
102 * @param units optional `units` attribute (omitted when null/empty).
103 */
104void detws_mtc_devices_add_item(DetwsMtcStreams *s, DetwsMtcCategory cat, const char *id, const char *type,
105 const char *name, const char *units);
106
107/** @brief Finish the probe document (close `<DataItems>` + `<Device>` + root). @return length, or 0 on overflow. */
108size_t detws_mtc_devices_end(DetwsMtcStreams *s);
109
110/**
111 * @brief Begin an MTConnectAssets (`asset`) document: XML declaration + header + open `<Assets>`.
112 * @param instance_id agent instanceId (boot id).
113 * @param asset_count the number of assets in this response (the Header `assetCount`).
114 * @param asset_buffer_size the agent's total asset capacity (the Header `assetBufferSize`).
115 *
116 * Reuses ::DetwsMtcStreams as the incremental buffer builder (as ::detws_mtc_devices_begin does).
117 */
118void detws_mtc_assets_begin(DetwsMtcStreams *s, char *buf, size_t cap, uint64_t instance_id, uint32_t asset_count,
119 uint32_t asset_buffer_size);
120
121/**
122 * @brief Open one `<CuttingTool>` asset and its `<CuttingToolLifeCycle>`.
123 * @param asset_id the CuttingTool `assetId` (required).
124 * @param serial_number optional `serialNumber` attribute (omitted when null/empty).
125 * @param tool_id optional `toolId` attribute (omitted when null/empty).
126 * @param device_uuid optional `deviceUuid` attribute (omitted when null/empty).
127 * @param timestamp optional ISO-8601 `timestamp` attribute (omitted when null/empty).
128 */
129void detws_mtc_assets_cutting_tool_begin(DetwsMtcStreams *s, const char *asset_id, const char *serial_number,
130 const char *tool_id, const char *device_uuid, const char *timestamp);
131
132/**
133 * @brief Add one `<ToolLife>` element to the open cutting tool's life cycle.
134 * @param type the life kind: "MINUTES", "PART_COUNT", or "WEAR".
135 * @param count_direction "UP" (accumulating) or "DOWN" (remaining).
136 * @param limit optional `limit` attribute (the max/threshold; omitted when null/empty).
137 * @param value the current life value text (XML-escaped).
138 */
139void detws_mtc_assets_tool_life(DetwsMtcStreams *s, const char *type, const char *count_direction, const char *limit,
140 const char *value);
141
142/** @brief Close the open `<CuttingToolLifeCycle>` + `<CuttingTool>`. */
143void detws_mtc_assets_cutting_tool_end(DetwsMtcStreams *s);
144
145/** @brief Finish the asset document (close `<Assets>` + root). @return length, or 0 on overflow. */
146size_t detws_mtc_assets_end(DetwsMtcStreams *s);
147
148// --- sample sequence cursor: a rolling observation buffer for the `sample` from/count long-poll ---
149
150/** @brief One buffered observation (a value at a sequence number), stored in fixed fields. */
151struct DetwsMtcObservation
152{
153 DetwsMtcCategory cat;
154 uint64_t seq; ///< the monotonic sequence number assigned when it was recorded.
155 char type[DETWS_MTC_STR_MAX + 1]; ///< DataItem type element name (e.g. "Position").
156 char data_id[DETWS_MTC_STR_MAX + 1];
157 char timestamp[DETWS_MTC_TS_MAX + 1];
158 char value[DETWS_MTC_VAL_MAX + 1];
159};
160
161/**
162 * @brief A fixed-size ring of the most recent observations, with the agent's sequence bookkeeping.
163 *
164 * Holds up to ::DETWS_MTC_SAMPLE_BUFFER observations. Each ::detws_mtc_sample_buffer_add assigns the
165 * next sequence number; when the ring is full the oldest is evicted and `first_seq` advances, so the
166 * retained window is always `[first_seq, next_seq)`. ::detws_mtc_sample_query then replays a requested
167 * sub-window as an MTConnectStreams document whose header carries firstSequence / lastSequence /
168 * nextSequence (MTC1.4 §6.7). Zero heap, single-owner (the caller serializes access).
169 */
170struct DetwsMtcSampleBuffer
171{
172 DetwsMtcObservation obs[DETWS_MTC_SAMPLE_BUFFER];
173 uint32_t count; ///< valid entries (<= DETWS_MTC_SAMPLE_BUFFER).
174 uint32_t head; ///< ring write index (next slot to fill).
175 uint64_t next_seq; ///< sequence the next add will assign (one past the newest).
176 uint64_t first_seq; ///< sequence of the oldest retained observation.
177};
178
179/**
180 * @brief Initialize an empty sample buffer.
181 * @param start_seq the first sequence number the agent will assign (0 is treated as 1).
182 */
183void detws_mtc_sample_buffer_init(DetwsMtcSampleBuffer *b, uint64_t start_seq);
184
185/**
186 * @brief Record one observation, assigning it the next sequence number (evicting the oldest if full).
187 * @return the sequence number assigned to this observation.
188 */
189uint64_t detws_mtc_sample_buffer_add(DetwsMtcSampleBuffer *b, DetwsMtcCategory cat, const char *type,
190 const char *data_id, const char *timestamp, const char *value);
191
192/**
193 * @brief Build the `sample` MTConnectStreams response for the window starting at @p from.
194 *
195 * Emits up to @p count observations from sequence @p from onward (a @p from below the retained
196 * firstSequence is clamped up to it - a stale subscriber catches up from the oldest kept, and the header
197 * firstSequence tells it data was dropped). The header reports firstSequence / lastSequence and a
198 * nextSequence the client uses to resume (the sequence past the last one returned, or the buffer's
199 * nextSequence when @p from is already at/after the newest). @return document length, or 0 on overflow.
200 */
201size_t detws_mtc_sample_query(DetwsMtcSampleBuffer *b, char *buf, size_t cap, uint64_t instance_id,
202 const char *device_name, uint64_t from, uint32_t count);
203
204#endif // DETWS_ENABLE_MTCONNECT
205#endif // DETERMINISTICESPASYNCWEBSERVER_MTCONNECT_H
User-facing configuration for DeterministicESPAsyncWebServer.
#define DETWS_MTC_VAL_MAX
#define DETWS_MTC_STR_MAX
#define DETWS_MTC_TS_MAX
#define DETWS_MTC_SAMPLE_BUFFER
MTConnect rolling sample buffer sizing (DETWS_ENABLE_MTCONNECT).