ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
mtconnect.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 mtconnect.h
6 * @brief MTConnect agent response codec (PROTOCORE_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 those response documents
10 * 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 * @author Douglas Quigg (dstroy0)
27 * @date 2026
28 */
29
30#ifndef PROTOCORE_MTCONNECT_H
31#define PROTOCORE_MTCONNECT_H
32
33#include "protocore_config.h" // the entry point: the enable gate below, and the widths
34
35#if PROTOCORE_ENABLE_MTCONNECT
36
38
39// PROTOCORE_MTCONNECT_BORROW - the bytes a document runs out of - is stated in protocore_config.h,
40// which sums it into the plaintext arena. A caller takes them once and passes the pointer to every
41// call. The borrow IS the document, so two documents are two borrows and never collide.
42
43/** @brief The MTConnect DataItem category (which stream element wraps the value). */
44typedef enum PROTO_ENUM_PACKED
45{
46 PROTOCORE_MTC_SAMPLE, ///< a measured value (<Samples>).
47 PROTOCORE_MTC_EVENT, ///< a discrete state (<Events>).
48 PROTOCORE_MTC_CONDITION ///< a condition (<Condition>): value is the sub-element name (Normal/Warning/Fault).
49} protocore_mtc_category;
50
51/** @brief The buffer a document is built into, and the agent identity its Header carries. */
52typedef struct
53{
54 char *out; ///< where the document lands
55 size_t cap; ///< how many bytes of it there are
56 uint64_t instance_id; ///< the agent's boot id (the Header instanceId)
57 const char *sender; ///< the agent's own name (the Header sender, MTC1.4 HeaderType)
58} MtConnectDocArgs;
59
60/** @brief What opening a streams document takes beyond the document itself. */
61typedef struct
62{
63 uint64_t next_seq; ///< the sequence the next observation will carry
64 const char *device_name; ///< the DeviceStream name
65 const char *device_uuid; ///< its uuid, which DeviceStreamType marks required
66 const char *component; ///< the ComponentStream component name
67 const char *component_id; ///< its id, which ComponentStreamType marks required
68} MtConnectStreamsArgs;
69
70/** @brief One observation added to the open component. */
71typedef struct
72{
73 protocore_mtc_category cat; ///< which container it belongs in
74 const char *type; ///< the DataItem type element name
75 const char *data_id; ///< its dataItemId
76 uint64_t seq; ///< its sequence number
77 const char *timestamp; ///< its ISO-8601 timestamp
78 const char *value; ///< its value, or the Condition sub-element name
79} MtConnectObsArgs;
80
81/** @brief The window a sample response reports, beyond the streams members. */
82typedef struct
83{
84 uint64_t first_seq; ///< the oldest sequence still retained
85 uint64_t last_seq; ///< the newest one written
86 uint32_t buffer_size; ///< how many observations the agent retains
87} MtConnectWindowArgs;
88
89/** @brief What opening a probe document takes. */
90typedef struct
91{
92 const char *device_id; ///< the Device id
93 const char *device_name; ///< its name
94 const char *uuid; ///< its uuid
95} MtConnectDeviceArgs;
96
97/** @brief One DataItem in the probe document. */
98typedef struct
99{
100 protocore_mtc_category cat; ///< its category attribute
101 const char *id; ///< its id
102 const char *type; ///< its type
103 const char *name; ///< optional name (omitted when null/empty)
104 const char *units; ///< optional units (omitted when null/empty)
105} MtConnectItemArgs;
106
107/** @brief What opening an asset document takes. */
108typedef struct
109{
110 uint32_t asset_count; ///< assets in this response (the Header assetCount)
111 uint32_t asset_buffer_size; ///< the agent's asset capacity (the Header assetBufferSize)
112} MtConnectAssetsArgs;
113
114/** @brief One CuttingTool and the status its life cycle opens with. */
115typedef struct
116{
117 const char *asset_id; ///< the CuttingTool assetId
118 const char *serial_number; ///< optional serialNumber
119 const char *tool_id; ///< optional toolId
120 const char *device_uuid; ///< optional deviceUuid
121 const char *timestamp; ///< optional ISO-8601 timestamp
122 const char *cutter_status; ///< the CutterStatus the life cycle opens with (minOccurs=1)
123} MtConnectToolArgs;
124
125/** @brief One ToolLife element. LifeType marks all four attributes required. */
126typedef struct
127{
128 const char *type; ///< "MINUTES", "PART_COUNT" or "WEAR"
129 const char *count_direction; ///< "UP" or "DOWN"
130 const char *initial; ///< the life the tool started with
131 const char *limit; ///< the threshold the count runs to
132 const char *value; ///< the current life
133} MtConnectLifeArgs;
134
135/** @brief The error an MTConnectError document reports. */
136typedef struct
137{
138 const char *error_code; ///< the errorCode attribute
139 const char *message; ///< the element text
140} MtConnectErrorArgs;
141
142/** @brief The sub-window a sample replay asks the ring for. */
143typedef struct
144{
145 uint64_t from; ///< the first sequence wanted
146 uint32_t count; ///< how many at most
147} MtConnectQueryArgs;
148
149/**
150 * @brief MTConnect (ANSI/MTC1.4) agent responses.
151 *
152 * A caller sets the members a call takes, invokes it through ::MtConnect with the bytes it runs out
153 * of, and reads the outcome off the same handle. How those bytes are carved is this module's and is
154 * never named here.
155 *
156 * MtConnect.doc.out = buf;
157 * MtConnect.doc.cap = sizeof(buf);
158 * MtConnect.doc.instance_id = 7;
159 * MtConnect.doc.sender = "agent-1";
160 * MtConnect.streams.next_seq = 100;
161 * MtConnect.streams.device_name = "VF2";
162 * MtConnect.streams.device_uuid = "uuid-1";
163 * MtConnect.streams_begin(work);
164 * MtConnect.obs.cat = PROTOCORE_MTC_SAMPLE;
165 * ...
166 * MtConnect.streams_add(work);
167 * MtConnect.streams_end(work);
168 * // MtConnect.n is the document length
169 *
170 * @var MtConnectNs::doc the buffer a document is built into, and the agent identity
171 * @var MtConnectNs::streams what opening a streams document takes
172 * @var MtConnectNs::obs one observation added to the open component
173 * @var MtConnectNs::window the window a sample response reports
174 * @var MtConnectNs::device what opening a probe document takes
175 * @var MtConnectNs::item one DataItem in the probe document
176 * @var MtConnectNs::assets what opening an asset document takes
177 * @var MtConnectNs::tool one CuttingTool and its opening status
178 * @var MtConnectNs::life one ToolLife element
179 * @var MtConnectNs::err the error an MTConnectError document reports
180 * @var MtConnectNs::query the sub-window a sample replay asks the ring for
181 * @var MtConnectNs::ok a call's true/false outcome
182 * @var MtConnectNs::n a finished document's length, or 0 when it did not fit
183 * @var MtConnectNs::seq the sequence number the last ring add assigned
184 * @var MtConnectNs::streams_begin open a streams document and its device + component
185 * @var MtConnectNs::streams_add add one observation to the open component
186 * @var MtConnectNs::streams_end close the component, the device and the document
187 * @var MtConnectNs::error build a whole MTConnectError document
188 * @var MtConnectNs::devices_begin open a probe document and its device
189 * @var MtConnectNs::devices_add add one DataItem to it
190 * @var MtConnectNs::devices_end close the probe document
191 * @var MtConnectNs::assets_begin open an asset document
192 * @var MtConnectNs::tool_begin open one CuttingTool and its life cycle
193 * @var MtConnectNs::tool_life add one ToolLife to the open life cycle
194 * @var MtConnectNs::tool_end close the life cycle and the tool
195 * @var MtConnectNs::assets_end close the asset document
196 * @var MtConnectNs::ring_init empty the observation ring and seat its first sequence
197 * @var MtConnectNs::ring_add record one observation, assigning it the next sequence
198 * @var MtConnectNs::ring_query replay a window of the ring as a streams document
199 *
200 * A ComponentStream is an xs:sequence of Samples, Events and Condition, each maxOccurs="1", so an
201 * observation is accumulated in the region its category owns and the three are written out in that
202 * order when the component closes. The containers are therefore never opened in arrival order, and a
203 * caller adds observations in whatever order it has them.
204 *
205 * @c work is PROTOCORE_MTCONNECT_BORROW plaintext bytes the CALLER took, at an address it knows. It
206 * is not held past the call. The borrow IS the document and the ring, so two
207 * agents are two borrows and never collide.
208 */
209typedef struct
210{
211 MtConnectDocArgs doc;
212 MtConnectStreamsArgs streams;
213 MtConnectObsArgs obs;
214 MtConnectWindowArgs window;
215 MtConnectDeviceArgs device;
216 MtConnectItemArgs item;
217 MtConnectAssetsArgs assets;
218 MtConnectToolArgs tool;
219 MtConnectLifeArgs life;
220 MtConnectErrorArgs err;
221 MtConnectQueryArgs query;
222 proto_bool ok;
223 size_t n;
224 uint64_t seq;
225} MtConnectVars;
226
227/** @brief The operands and the outcome. */
228extern MtConnectVars MtConnectV;
229
230/** @brief The entries. */
231typedef struct
232{
233 void (*const streams_begin)(uint8_t *work);
234 void (*const streams_add)(uint8_t *work);
235 void (*const streams_end)(uint8_t *work);
236 void (*const error)(uint8_t *work);
237 void (*const devices_begin)(uint8_t *work);
238 void (*const devices_add)(uint8_t *work);
239 void (*const devices_end)(uint8_t *work);
240 void (*const assets_begin)(uint8_t *work);
241 void (*const tool_begin)(uint8_t *work);
242 void (*const tool_life)(uint8_t *work);
243 void (*const tool_end)(uint8_t *work);
244 void (*const assets_end)(uint8_t *work);
245 void (*const ring_init)(uint8_t *work);
246 void (*const ring_add)(uint8_t *work);
247 void (*const ring_query)(uint8_t *work);
248} MtConnectNs;
249
250// What the table binds, defined once in the .c and taking one parameter each: everything
251// else an entry needs is an operand in MtConnectV or a region of the borrow at a fixed offset.
252void protocore_mt_connect_streams_begin(uint8_t *work);
253void protocore_mt_connect_streams_add(uint8_t *work);
254void protocore_mt_connect_streams_end(uint8_t *work);
255void protocore_mt_connect_error(uint8_t *work);
256void protocore_mt_connect_devices_begin(uint8_t *work);
257void protocore_mt_connect_devices_add(uint8_t *work);
258void protocore_mt_connect_devices_end(uint8_t *work);
259void protocore_mt_connect_assets_begin(uint8_t *work);
260void protocore_mt_connect_tool_begin(uint8_t *work);
261void protocore_mt_connect_tool_life(uint8_t *work);
262void protocore_mt_connect_tool_end(uint8_t *work);
263void protocore_mt_connect_assets_end(uint8_t *work);
264void protocore_mt_connect_ring_init(uint8_t *work);
265void protocore_mt_connect_ring_add(uint8_t *work);
266void protocore_mt_connect_ring_query(uint8_t *work);
267
268// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
269// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
270// `MtConnect.streams_begin(work)` resolves to a named function and becomes a DIRECT call. An extern table
271// leaves the call indirect and the symbol live at every level, -O2 -flto included.
272static const MtConnectNs MtConnect __attribute__((unused)) = {
273 .streams_begin = protocore_mt_connect_streams_begin,
274 .streams_add = protocore_mt_connect_streams_add,
275 .streams_end = protocore_mt_connect_streams_end,
276 .error = protocore_mt_connect_error,
277 .devices_begin = protocore_mt_connect_devices_begin,
278 .devices_add = protocore_mt_connect_devices_add,
279 .devices_end = protocore_mt_connect_devices_end,
280 .assets_begin = protocore_mt_connect_assets_begin,
281 .tool_begin = protocore_mt_connect_tool_begin,
282 .tool_life = protocore_mt_connect_tool_life,
283 .tool_end = protocore_mt_connect_tool_end,
284 .assets_end = protocore_mt_connect_assets_end,
285 .ring_init = protocore_mt_connect_ring_init,
286 .ring_add = protocore_mt_connect_ring_add,
287 .ring_query = protocore_mt_connect_ring_query,
288};
289
290/**
291 * @brief The bytes every entry here runs out of: the running document and the observation ring.
292 *
293 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where that
294 * borrow comes from. Taken once from the end of the plaintext pool, which no mark and no release
295 * walks, because the ring outlives the documents replayed out of it.
296 *
297 * @return the span.
298 */
299uint8_t *protocore_mtconnect_span(void);
300
302
303#endif // PROTOCORE_ENABLE_MTCONNECT
304
305#endif // PROTOCORE_MTCONNECT_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
#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