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