DeterministicESPAsyncWebServer v6.28.0
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
umati.cpp
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 umati.cpp
6 * @brief umati (OPC UA for Machine Tools, OPC 40501-1) MachineTool model - resolver implementation.
7 *
8 * A fixed node table (no heap): each container node (MachineTool, Identification, Monitoring, its
9 * sub-objects, Production, Notification) answers a Browse with its child ReferenceDescriptions, and
10 * each leaf Variable answers a Read out of the bound UmatiMachineTool struct. The Objects folder
11 * (ns0 i=85) organizes the MachineTool so a client discovers it from the root.
12 */
13
15
16#if DETWS_ENABLE_UMATI
17
18#include <string.h> // strnlen (string.h is allowed; the no-stdlib rule is about stdlib.h/malloc)
19
20// ---------------------------------------------------------------------------
21// Node identifiers (namespace DETWS_UMATI_NS). Objects end in 0; their variables count up from it.
22// ---------------------------------------------------------------------------
23namespace
24{
25enum : uint32_t
26{
27 MACHINETOOL = 5000,
28
29 IDENTIFICATION = 5100,
30 ID_MANUFACTURER = 5101,
31 ID_MODEL = 5102,
32 ID_SERIAL = 5103,
33 ID_YEAR = 5104,
34 ID_SWREV = 5105,
35 ID_PRODURI = 5106,
36
37 MONITORING = 5200,
38 MON_MACHINE = 5210,
39 MON_OPMODE = 5211,
40 MON_POWERON = 5212,
41 MON_CHANNEL = 5220,
42 CH_STATE = 5221,
43 CH_FEEDOVR = 5222,
44 CH_RAPIDOVR = 5223,
45 CH_ACTIVEPROG = 5224,
46 MON_SPINDLE = 5230,
47 SP_SPEED = 5231,
48 SP_OVERRIDE = 5232,
49 SP_ROTATING = 5233,
50 MON_AXIS_X = 5240,
51 AX_X_POS = 5241,
52 MON_AXIS_Y = 5250,
53 AX_Y_POS = 5251,
54 MON_AXIS_Z = 5260,
55 AX_Z_POS = 5261,
56
57 PRODUCTION = 5300,
58 PROD_ACTIVEPROG = 5301,
59 PROD_PARTCOUNT = 5302,
60
61 NOTIFICATION = 5400,
62 NOTIF_MESSAGE = 5401,
63 NOTIF_SEVERITY = 5402,
64};
65
66// All MachineTool model state, owned by one instance (internal linkage): the bound machine-data
67// pointer the resolvers read from. Null until umati_bind(); a Read/Browse before binding is a clean
68// miss (BadNodeIdUnknown), so the server never dereferences a null model.
69struct UmatiCtx
70{
71 const UmatiMachineTool *mt;
72};
73UmatiCtx s_umati = {nullptr};
74
75// --- Variant fillers (leaf values) -----------------------------------------
76void set_str(OpcUaVariant *o, const char *s)
77{
78 o->type = OpcUaVariantType::OPCUA_VAR_STRING;
79 o->str = s ? s : "";
80 o->str_len = (int32_t)strnlen(o->str, 0xFFFF); // bound the scan: a model string is a caller-owned C string
81}
82void set_u32(OpcUaVariant *o, uint32_t v)
83{
84 o->type = OpcUaVariantType::OPCUA_VAR_UINT32;
85 o->u32 = v;
86}
87void set_i32(OpcUaVariant *o, int32_t v)
88{
89 o->type = OpcUaVariantType::OPCUA_VAR_INT32;
90 o->i32 = v;
91}
92void set_f64(OpcUaVariant *o, double v)
93{
94 o->type = OpcUaVariantType::OPCUA_VAR_DOUBLE;
95 o->f64 = v;
96}
97void set_bool(OpcUaVariant *o, bool v)
98{
99 o->type = OpcUaVariantType::OPCUA_VAR_BOOL;
100 o->b = v;
101}
102
103// --- Browse helpers --------------------------------------------------------
104// Append one ReferenceDescription (bounded by @p max). BrowseName + DisplayName share @p name; every
105// reference is a forward HasComponent (containment) unless @p organizes (the Objects->MachineTool link).
106int32_t add_ref(OpcUaReference *out, int32_t n, uint32_t max, uint32_t target_id, const char *name, uint32_t node_class,
107 bool organizes = false)
108{
109 if ((uint32_t)n >= max)
110 return n;
111 OpcUaReference *r = &out[n];
112 r->ref_type_id = organizes ? OPCUA_REFTYPE_ORGANIZES : OPCUA_REFTYPE_HAS_COMPONENT;
113 r->is_forward = true;
114 r->target_ns = DETWS_UMATI_NS;
115 r->target_id = target_id;
116 r->browse_name_ns = DETWS_UMATI_NS;
117 r->browse_name = name;
118 r->display_name = name;
119 r->node_class = node_class;
120 r->type_def_id =
121 (node_class == OPCUA_NODECLASS_VARIABLE) ? OPCUA_TYPEDEF_BASE_DATA_VARIABLE : OPCUA_TYPEDEF_BASE_OBJECT;
122 return n + 1;
123}
124int32_t add_obj(OpcUaReference *out, int32_t n, uint32_t max, uint32_t id, const char *name)
125{
126 return add_ref(out, n, max, id, name, OPCUA_NODECLASS_OBJECT);
127}
128int32_t add_var(OpcUaReference *out, int32_t n, uint32_t max, uint32_t id, const char *name)
129{
130 return add_ref(out, n, max, id, name, OPCUA_NODECLASS_VARIABLE);
131}
132} // namespace
133
134// ---------------------------------------------------------------------------
135// Public API
136// ---------------------------------------------------------------------------
137void umati_bind(const UmatiMachineTool *mt)
138{
139 s_umati.mt = mt;
140}
141
142bool umati_read(uint16_t ns, uint32_t id, uint32_t attribute, OpcUaVariant *out)
143{
144 const UmatiMachineTool *mt = s_umati.mt;
145 if (!mt || ns != DETWS_UMATI_NS || attribute != OPCUA_ATTR_VALUE)
146 return false;
147 switch (id)
148 {
149 // Identification
150 case ID_MANUFACTURER:
151 set_str(out, mt->ident.manufacturer);
152 return true;
153 case ID_MODEL:
154 set_str(out, mt->ident.model);
155 return true;
156 case ID_SERIAL:
157 set_str(out, mt->ident.serial_number);
158 return true;
159 case ID_YEAR:
160 set_u32(out, mt->ident.year_of_construction);
161 return true;
162 case ID_SWREV:
163 set_str(out, mt->ident.software_revision);
164 return true;
165 case ID_PRODURI:
166 set_str(out, mt->ident.product_instance_uri);
167 return true;
168 // Monitoring / MachineTool
169 case MON_OPMODE:
170 set_i32(out, (int32_t)mt->operation_mode);
171 return true;
172 case MON_POWERON:
173 set_f64(out, mt->power_on_duration_s);
174 return true;
175 // Monitoring / Channel
176 case CH_STATE:
177 set_i32(out, (int32_t)mt->channel.state);
178 return true;
179 case CH_FEEDOVR:
180 set_f64(out, mt->channel.feed_override);
181 return true;
182 case CH_RAPIDOVR:
183 set_f64(out, mt->channel.rapid_override);
184 return true;
185 case CH_ACTIVEPROG:
186 set_str(out, mt->channel.active_program);
187 return true;
188 // Monitoring / Spindle
189 case SP_SPEED:
190 set_f64(out, mt->spindle.rotation_speed);
191 return true;
192 case SP_OVERRIDE:
193 set_f64(out, mt->spindle.override_value);
194 return true;
195 case SP_ROTATING:
196 set_bool(out, mt->spindle.is_rotating);
197 return true;
198 // Monitoring / Axes
199 case AX_X_POS:
200 set_f64(out, mt->axis_x.actual_position);
201 return true;
202 case AX_Y_POS:
203 set_f64(out, mt->axis_y.actual_position);
204 return true;
205 case AX_Z_POS:
206 set_f64(out, mt->axis_z.actual_position);
207 return true;
208 // Production
209 case PROD_ACTIVEPROG:
210 set_str(out, mt->active_program);
211 return true;
212 case PROD_PARTCOUNT:
213 set_u32(out, mt->produced_part_count);
214 return true;
215 // Notification
216 case NOTIF_MESSAGE:
217 set_str(out, mt->message_text);
218 return true;
219 case NOTIF_SEVERITY:
220 set_u32(out, mt->message_severity);
221 return true;
222 default:
223 return false;
224 }
225}
226
227int32_t umati_browse(uint16_t ns, uint32_t id, OpcUaReference *out, uint32_t max)
228{
229 const UmatiMachineTool *mt = s_umati.mt;
230 if (!mt)
231 return -1;
232
233 // The Objects folder (ns0 i=85) organizes the MachineTool so a client finds it from the root.
234 if (ns == 0 && id == 85)
235 return add_ref(out, 0, max, MACHINETOOL, mt->name ? mt->name : "MachineTool", OPCUA_NODECLASS_OBJECT,
236 /*organizes=*/true);
237
238 if (ns != DETWS_UMATI_NS)
239 return -1;
240
241 int32_t n = 0;
242 switch (id)
243 {
244 case MACHINETOOL:
245 n = add_obj(out, n, max, IDENTIFICATION, "Identification");
246 n = add_obj(out, n, max, MONITORING, "Monitoring");
247 n = add_obj(out, n, max, PRODUCTION, "Production");
248 n = add_obj(out, n, max, NOTIFICATION, "Notification");
249 return n;
250 case IDENTIFICATION:
251 n = add_var(out, n, max, ID_MANUFACTURER, "Manufacturer");
252 n = add_var(out, n, max, ID_MODEL, "Model");
253 n = add_var(out, n, max, ID_SERIAL, "SerialNumber");
254 n = add_var(out, n, max, ID_YEAR, "YearOfConstruction");
255 n = add_var(out, n, max, ID_SWREV, "SoftwareRevision");
256 n = add_var(out, n, max, ID_PRODURI, "ProductInstanceUri");
257 return n;
258 case MONITORING:
259 n = add_obj(out, n, max, MON_MACHINE, "MachineTool");
260 n = add_obj(out, n, max, MON_CHANNEL, "Channel");
261 n = add_obj(out, n, max, MON_SPINDLE, "Spindle");
262 n = add_obj(out, n, max, MON_AXIS_X, "Axis_X");
263 n = add_obj(out, n, max, MON_AXIS_Y, "Axis_Y");
264 n = add_obj(out, n, max, MON_AXIS_Z, "Axis_Z");
265 return n;
266 case MON_MACHINE:
267 n = add_var(out, n, max, MON_OPMODE, "OperationMode");
268 n = add_var(out, n, max, MON_POWERON, "PowerOnDuration");
269 return n;
270 case MON_CHANNEL:
271 n = add_var(out, n, max, CH_STATE, "ChannelState");
272 n = add_var(out, n, max, CH_FEEDOVR, "FeedOverride");
273 n = add_var(out, n, max, CH_RAPIDOVR, "RapidOverride");
274 n = add_var(out, n, max, CH_ACTIVEPROG, "ActiveProgram");
275 return n;
276 case MON_SPINDLE:
277 n = add_var(out, n, max, SP_SPEED, "RotationSpeed");
278 n = add_var(out, n, max, SP_OVERRIDE, "OverrideValue");
279 n = add_var(out, n, max, SP_ROTATING, "IsRotating");
280 return n;
281 case MON_AXIS_X:
282 return add_var(out, 0, max, AX_X_POS, "ActualPosition");
283 case MON_AXIS_Y:
284 return add_var(out, 0, max, AX_Y_POS, "ActualPosition");
285 case MON_AXIS_Z:
286 return add_var(out, 0, max, AX_Z_POS, "ActualPosition");
287 case PRODUCTION:
288 n = add_var(out, n, max, PROD_ACTIVEPROG, "ActiveProgram");
289 n = add_var(out, n, max, PROD_PARTCOUNT, "ProducedPartCount");
290 return n;
291 case NOTIFICATION:
292 n = add_var(out, n, max, NOTIF_MESSAGE, "ActiveMessage");
293 n = add_var(out, n, max, NOTIF_SEVERITY, "Severity");
294 return n;
295 default:
296 return -1; // a leaf Variable (no children) or an unknown node
297 }
298}
299
300void umati_install(const UmatiMachineTool *mt)
301{
302 umati_bind(mt);
303 opcua_set_read_handler(umati_read);
304 opcua_set_browse_handler(umati_browse);
305}
306
307#endif // DETWS_ENABLE_UMATI
#define DETWS_UMATI_NS
NamespaceIndex the umati MachineTool nodes live at (default 1).
umati - OPC UA for Machine Tools (OPC 40501-1) information model (DETWS_ENABLE_UMATI).