ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
euromap77.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 euromap77.cpp
6 * @brief EUROMAP 77 (OPC 40077) IMM_MES_Interface model - resolver implementation.
7 *
8 * A fixed node table (no heap): each container node (IMM_MES_Interface, MachineInformation,
9 * MachineStatus, Jobs, ActiveJob, ActiveJobValues) answers a Browse with its child ReferenceDescriptions,
10 * and each leaf Variable answers a Read out of the bound EmImm struct. The Objects folder (ns0 i=85)
11 * organizes the IMM_MES_Interface so a client discovers it from the root. Production counters are served
12 * as faithful UInt64 (EUROMAP 77 defines them 64-bit).
13 */
14
16
17#if PC_ENABLE_EUROMAP77
18
19#include <string.h> // strnlen (string.h is allowed; the no-stdlib rule is about stdlib.h/malloc)
20
21// ---------------------------------------------------------------------------
22// Node identifiers (namespace PC_EM77_NS). Objects end in 0; their variables count up from it.
23// ---------------------------------------------------------------------------
24namespace
25{
26enum : uint32_t // NOSONAR(cpp:S3642): anonymous table of OPC-UA node ids used as bare uint32_t (arithmetic + wire
27 // compares); enum class would force a cast at every use
28{
29 IMM_MES_INTERFACE = 7000,
30
31 MACHINEINFORMATION = 7100,
32 MI_MANUFACTURER = 7101,
33 MI_MODEL = 7102,
34 MI_SERIAL = 7103,
35 MI_PRODUCTCODE = 7104,
36 MI_HWREV = 7105,
37 MI_SWREV = 7106,
38 MI_DEVREV = 7107,
39 MI_MANUFACTURERURI = 7108,
40
41 MACHINESTATUS = 7200,
42 MS_ISPRESENT = 7201,
43 MS_MACHINEMODE = 7202,
44
45 JOBS = 7300,
46 ACTIVEJOB = 7310,
47 AJ_JOBNAME = 7311,
48 AJ_JOBDESC = 7312,
49 AJ_MATERIAL = 7313,
50 AJ_PRODUCTNAME = 7314,
51 AJ_MOULDID = 7315,
52 AJ_EXPECTEDCYCLE = 7316,
53 AJ_NUMCAVITIES = 7317,
54 AJ_NOMINALPARTS = 7318,
55 ACTIVEJOBVALUES = 7320,
56 AJV_JOBCYCLECOUNTER = 7321,
57 AJV_MACHINECYCLECOUNTER = 7322,
58 AJV_LASTCYCLETIME = 7323,
59 AJV_AVERAGECYCLETIME = 7324,
60 AJV_JOBPARTSCOUNTER = 7325,
61 AJV_JOBGOODPARTSCOUNTER = 7326,
62 AJV_JOBBADPARTSCOUNTER = 7327,
63 AJV_JOBSTATUS = 7328,
64};
65
66// All EUROMAP 77 model state, owned by one instance (internal linkage): the bound IMM pointer the
67// resolvers read from. Null until pc_em77_bind(); a Read/Browse before binding is a clean miss
68// (BadNodeIdUnknown), so the server never dereferences a null model.
69struct EuroMap77Ctx
70{
71 const EmImm *imm;
72};
73EuroMap77Ctx s_em77 = {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_u64(OpcUaVariant *o, uint64_t v)
88{
89 o->type = OpcUaVariantType::OPCUA_VAR_UINT64;
90 o->u64 = v;
91}
92void set_i32(OpcUaVariant *o, int32_t v)
93{
94 o->type = OpcUaVariantType::OPCUA_VAR_INT32;
95 o->i32 = v;
96}
97void set_f64(OpcUaVariant *o, double v)
98{
99 o->type = OpcUaVariantType::OPCUA_VAR_DOUBLE;
100 o->f64 = v;
101}
102void set_bool(OpcUaVariant *o, bool v)
103{
104 o->type = OpcUaVariantType::OPCUA_VAR_BOOL;
105 o->b = v;
106}
107
108// --- Browse helpers --------------------------------------------------------
109// Append one ReferenceDescription (bounded by @p max). BrowseName + DisplayName share @p name; every
110// reference is a forward HasComponent (containment) unless @p organizes (the Objects->IMM link).
111int32_t add_ref(OpcUaReference *out, int32_t n, uint32_t max, uint32_t target_id, const char *name, uint32_t node_class,
112 bool organizes = false)
113{
114 if ((uint32_t)n >= max)
115 {
116 return n;
117 }
118 OpcUaReference *r = &out[n];
119 r->ref_type_id = organizes ? OPCUA_REFTYPE_ORGANIZES : OPCUA_REFTYPE_HAS_COMPONENT;
120 r->is_forward = true;
121 r->target_ns = PC_EM77_NS;
122 r->target_id = target_id;
123 r->browse_name_ns = PC_EM77_NS;
124 r->browse_name = name;
125 r->display_name = name;
126 r->node_class = node_class;
127 r->type_def_id =
128 (node_class == OPCUA_NODECLASS_VARIABLE) ? OPCUA_TYPEDEF_BASE_DATA_VARIABLE : OPCUA_TYPEDEF_BASE_OBJECT;
129 return n + 1;
130}
131int32_t add_obj(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_OBJECT);
134}
135int32_t add_var(OpcUaReference *out, int32_t n, uint32_t max, uint32_t id, const char *name)
136{
137 return add_ref(out, n, max, id, name, OPCUA_NODECLASS_VARIABLE);
138}
139} // namespace
140
141// ---------------------------------------------------------------------------
142// Public API
143// ---------------------------------------------------------------------------
144void pc_em77_bind(const EmImm *imm)
145{
146 s_em77.imm = imm;
147}
148
149bool pc_em77_read(uint16_t ns, uint32_t id, uint32_t attribute, OpcUaVariant *out)
150{
151 const EmImm *imm = s_em77.imm;
152 if (!imm || ns != PC_EM77_NS || attribute != OPCUA_ATTR_VALUE)
153 {
154 return false;
155 }
156 switch (id)
157 {
158 // MachineInformation
159 case MI_MANUFACTURER:
160 set_str(out, imm->info.manufacturer);
161 return true;
162 case MI_MODEL:
163 set_str(out, imm->info.model);
164 return true;
165 case MI_SERIAL:
166 set_str(out, imm->info.serial_number);
167 return true;
168 case MI_PRODUCTCODE:
169 set_str(out, imm->info.product_code);
170 return true;
171 case MI_HWREV:
172 set_str(out, imm->info.hardware_revision);
173 return true;
174 case MI_SWREV:
175 set_str(out, imm->info.software_revision);
176 return true;
177 case MI_DEVREV:
178 set_str(out, imm->info.device_revision);
179 return true;
180 case MI_MANUFACTURERURI:
181 set_str(out, imm->info.manufacturer_uri);
182 return true;
183 // MachineStatus
184 case MS_ISPRESENT:
185 set_bool(out, imm->status.is_present);
186 return true;
187 case MS_MACHINEMODE:
188 set_i32(out, (int32_t)imm->status.machine_mode);
189 return true;
190 // Jobs / ActiveJob
191 case AJ_JOBNAME:
192 set_str(out, imm->active_job.job_name);
193 return true;
194 case AJ_JOBDESC:
195 set_str(out, imm->active_job.job_description);
196 return true;
197 case AJ_MATERIAL:
198 set_str(out, imm->active_job.material);
199 return true;
200 case AJ_PRODUCTNAME:
201 set_str(out, imm->active_job.product_name);
202 return true;
203 case AJ_MOULDID:
204 set_str(out, imm->active_job.mould_id);
205 return true;
206 case AJ_EXPECTEDCYCLE:
207 set_f64(out, imm->active_job.expected_cycle_time);
208 return true;
209 case AJ_NUMCAVITIES:
210 set_u32(out, imm->active_job.num_cavities);
211 return true;
212 case AJ_NOMINALPARTS:
213 set_u64(out, imm->active_job.nominal_parts);
214 return true;
215 // Jobs / ActiveJobValues
216 case AJV_JOBCYCLECOUNTER:
217 set_u64(out, imm->active_job_values.job_cycle_counter);
218 return true;
219 case AJV_MACHINECYCLECOUNTER:
220 set_u64(out, imm->active_job_values.machine_cycle_counter);
221 return true;
222 case AJV_LASTCYCLETIME:
223 set_f64(out, imm->active_job_values.last_cycle_time);
224 return true;
225 case AJV_AVERAGECYCLETIME:
226 set_f64(out, imm->active_job_values.average_cycle_time);
227 return true;
228 case AJV_JOBPARTSCOUNTER:
229 set_u64(out, imm->active_job_values.job_parts_counter);
230 return true;
231 case AJV_JOBGOODPARTSCOUNTER:
232 set_u64(out, imm->active_job_values.job_good_parts_counter);
233 return true;
234 case AJV_JOBBADPARTSCOUNTER:
235 set_u64(out, imm->active_job_values.job_bad_parts_counter);
236 return true;
237 case AJV_JOBSTATUS:
238 set_i32(out, (int32_t)imm->active_job_values.job_status);
239 return true;
240 default:
241 return false;
242 }
243}
244
245int32_t pc_em77_browse(uint16_t ns, uint32_t id, OpcUaReference *out, uint32_t max)
246{
247 const EmImm *imm = s_em77.imm;
248 if (!imm)
249 {
250 return -1;
251 }
252
253 // The Objects folder (ns0 i=85) organizes the IMM_MES_Interface so a client finds it from the root.
254 if (ns == 0 && id == 85)
255 {
256 return add_ref(out, 0, max, IMM_MES_INTERFACE, imm->name ? imm->name : "IMM_MES_Interface",
257 OPCUA_NODECLASS_OBJECT, /*organizes=*/true);
258 }
259
260 if (ns != PC_EM77_NS)
261 {
262 return -1;
263 }
264
265 int32_t n = 0;
266 switch (id)
267 {
268 case IMM_MES_INTERFACE:
269 n = add_obj(out, n, max, MACHINEINFORMATION, "MachineInformation");
270 n = add_obj(out, n, max, MACHINESTATUS, "MachineStatus");
271 n = add_obj(out, n, max, JOBS, "Jobs");
272 return n;
273 case MACHINEINFORMATION:
274 n = add_var(out, n, max, MI_MANUFACTURER, "Manufacturer");
275 n = add_var(out, n, max, MI_MODEL, "Model");
276 n = add_var(out, n, max, MI_SERIAL, "SerialNumber");
277 n = add_var(out, n, max, MI_PRODUCTCODE, "ProductCode");
278 n = add_var(out, n, max, MI_HWREV, "HardwareRevision");
279 n = add_var(out, n, max, MI_SWREV, "SoftwareRevision");
280 n = add_var(out, n, max, MI_DEVREV, "DeviceRevision");
281 n = add_var(out, n, max, MI_MANUFACTURERURI, "ManufacturerUri");
282 return n;
283 case MACHINESTATUS:
284 n = add_var(out, n, max, MS_ISPRESENT, "IsPresent");
285 n = add_var(out, n, max, MS_MACHINEMODE, "MachineMode");
286 return n;
287 case JOBS:
288 n = add_obj(out, n, max, ACTIVEJOB, "ActiveJob");
289 n = add_obj(out, n, max, ACTIVEJOBVALUES, "ActiveJobValues");
290 return n;
291 case ACTIVEJOB:
292 n = add_var(out, n, max, AJ_JOBNAME, "JobName");
293 n = add_var(out, n, max, AJ_JOBDESC, "JobDescription");
294 n = add_var(out, n, max, AJ_MATERIAL, "Material");
295 n = add_var(out, n, max, AJ_PRODUCTNAME, "ProductName");
296 n = add_var(out, n, max, AJ_MOULDID, "MouldId");
297 n = add_var(out, n, max, AJ_EXPECTEDCYCLE, "ExpectedCycleTime");
298 n = add_var(out, n, max, AJ_NUMCAVITIES, "NumCavities");
299 n = add_var(out, n, max, AJ_NOMINALPARTS, "NominalParts");
300 return n;
301 case ACTIVEJOBVALUES:
302 n = add_var(out, n, max, AJV_JOBCYCLECOUNTER, "JobCycleCounter");
303 n = add_var(out, n, max, AJV_MACHINECYCLECOUNTER, "MachineCycleCounter");
304 n = add_var(out, n, max, AJV_LASTCYCLETIME, "LastCycleTime");
305 n = add_var(out, n, max, AJV_AVERAGECYCLETIME, "AverageCycleTime");
306 n = add_var(out, n, max, AJV_JOBPARTSCOUNTER, "JobPartsCounter");
307 n = add_var(out, n, max, AJV_JOBGOODPARTSCOUNTER, "JobGoodPartsCounter");
308 n = add_var(out, n, max, AJV_JOBBADPARTSCOUNTER, "JobBadPartsCounter");
309 n = add_var(out, n, max, AJV_JOBSTATUS, "JobStatus");
310 return n;
311 default:
312 return -1; // a leaf Variable (no children) or an unknown node
313 }
314}
315
316void pc_em77_install(const EmImm *imm)
317{
318 pc_em77_bind(imm);
319 pc_opcua_set_read_handler(pc_em77_read);
320 pc_opcua_set_browse_handler(pc_em77_browse);
321}
322
323#endif // PC_ENABLE_EUROMAP77
EUROMAP 77 / OPC 40077 - OPC UA for injection moulding machines (IMM <-> MES), the IMM_MES_Interface ...
#define PC_EM77_NS
NamespaceIndex the EUROMAP 77 IMM_MES_Interface nodes live at (default 1).