@vitormnm/node-red-simple-opcua 1.4.3 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/client/icons/opcua.svg +132 -132
  2. package/client/lib/opcua-client-browser.js +368 -330
  3. package/client/lib/opcua-client-method-service.js +88 -88
  4. package/client/lib/opcua-client-read-service.js +27 -15
  5. package/client/lib/opcua-client-subscription-id-service.js +24 -24
  6. package/client/lib/opcua-client-subscription-service.js +175 -170
  7. package/client/lib/opcua-client-write-service.js +146 -146
  8. package/client/opcua-client-config.html +80 -80
  9. package/client/opcua-client-utils.js +217 -22
  10. package/client/opcua-client.html +140 -140
  11. package/client/view/opcua-client.js +1144 -1140
  12. package/examples/flows_simple_opc.json +1 -2851
  13. package/icons/opcua.svg +132 -132
  14. package/icons/opcua2.svg +132 -132
  15. package/package.json +3 -3
  16. package/server/icons/opcua.svg +132 -132
  17. package/server/lib/opcua-address-space-alarm.js +341 -341
  18. package/server/lib/opcua-address-space-builder.js +1797 -1485
  19. package/server/lib/opcua-config.js +800 -546
  20. package/server/lib/opcua-constants.js +120 -109
  21. package/server/lib/opcua-server-events-child.js +139 -139
  22. package/server/lib/opcua-server-methods.js +2 -0
  23. package/server/lib/opcua-server-runtime-child.js +874 -819
  24. package/server/lib/opcua-server-runtime.js +385 -311
  25. package/server/lib/opcua-server-status-child.js +187 -187
  26. package/server/lib/server-node-utils.js +16 -16
  27. package/server/opcua-server-io.html +346 -346
  28. package/server/opcua-server-io.js +497 -496
  29. package/server/opcua-server-registry.js +270 -270
  30. package/server/opcua-server.css +265 -265
  31. package/server/opcua-server.html +158 -1643
  32. package/server/opcua-server.js +24 -13
  33. package/server/view/opcua-server.css +496 -0
  34. package/server/view/opcua-server.js +1585 -0
@@ -1,1485 +1,1797 @@
1
- "use strict";
2
-
3
- const {
4
- DATA_TYPE_MAP,
5
- StatusCodes,
6
- Variant,
7
- VariantArrayType,
8
- sanitizeNodeIdPath,
9
- DataType,
10
- coerceNodeId
11
- } = require("./opcua-constants");
12
-
13
- const { OpcUaAddressSpaceAlarm } = require("./opcua-address-space-alarm")
14
-
15
- class OpcUaAddressSpaceBuilder {
16
- constructor(options) {
17
- this.namespace = options.namespace;
18
- this.namespaces = options.namespaces || new Map([[2, this.namespace]]);
19
- this.server = options.server;
20
- this.registry = options.registry;
21
- this.node = options.node;
22
- this.serverName = options.serverName;
23
- this.nodeEntries = new Map();
24
- this.variableStore = new Map();
25
- this.variableNodeIdStore = new Map();
26
- this.objectTypeStore = new Map();
27
- this.alarmStore = new Map();
28
- this.pendingAlarms = [];
29
-
30
- this.addressSpace = options.addressSpace;
31
-
32
-
33
-
34
- this.addressSpaceAlarm = new OpcUaAddressSpaceAlarm({
35
- namespace: this.namespace,
36
- registry: this.registry,
37
- server: this.server,
38
- node: this.node
39
- });
40
- }
41
-
42
- rebuild(treeConfig) {
43
-
44
- this.sync(treeConfig, { fullReset: true });
45
- }
46
-
47
- sync(treeConfig, options) {
48
- if (!this.namespace || !this.server) {
49
- throw new Error("Address space is not initialized");
50
- }
51
-
52
- const settings = options || {};
53
- const desiredEntries = this.collectDesiredEntries(treeConfig);
54
-
55
-
56
-
57
- if (settings.fullReset) {
58
- this.clearDynamicNodes();
59
- }
60
-
61
- const removalRoots = this.collectRemovalRoots(desiredEntries);
62
- removalRoots.forEach((path) => this.removeSubtree(path));
63
-
64
-
65
-
66
- this.updateExistingNodes(desiredEntries);
67
- this.createMissingNodes(desiredEntries);
68
- this.flushPendingAlarms();
69
-
70
- }
71
-
72
-
73
-
74
- clearDynamicNodes() {
75
- const paths = Array.from(this.nodeEntries.keys()).sort(comparePathDepthDesc);
76
-
77
- paths.forEach((path) => this.removeSingleNode(path));
78
- this.nodeEntries.clear();
79
- this.variableStore.clear();
80
- this.variableNodeIdStore.clear();
81
- this.objectTypeStore.clear();
82
- this.alarmStore.clear(); // Adicione esta linha
83
- this.pendingAlarms = [];
84
- }
85
-
86
- readValueByPath(path) {
87
- return this.getVariableRecordByPath(path).getValue();
88
- }
89
-
90
- readValueByNodeId(nodeId) {
91
- return this.getVariableRecordByNodeId(nodeId).getValue();
92
- }
93
-
94
- eventValueByPath(valuePayload) {
95
-
96
- const path = valuePayload.nodePath
97
- const value = valuePayload.value
98
- const message = valuePayload.message
99
- const severity = valuePayload.severity
100
- if (!this.nodeEntries.has(path)) {
101
- throw new Error("Unknown path: " + path);
102
- }
103
-
104
- const entry = this.nodeEntries.get(path);
105
-
106
-
107
-
108
- entry.node.raiseEvent("BaseEventType", {
109
- sourceName: {
110
- dataType: DataType.String,
111
- value: path
112
- },
113
- message: {
114
- dataType: DataType.LocalizedText,
115
- value: { text: message }
116
- },
117
- severity: {
118
- dataType: DataType.UInt16,
119
- value: severity
120
- }
121
- });
122
-
123
- }
124
-
125
- writeValueByPath(path, value) {
126
- return this.getVariableRecordByPath(path).setValue(value);
127
- }
128
-
129
- writeValueByNodeId(nodeId, value) {
130
- return this.getVariableRecordByNodeId(nodeId).setValue(value);
131
- }
132
-
133
- readValue(identifierType, identifier) {
134
- return this.getVariableRecord(identifierType, identifier).getValue();
135
- }
136
-
137
- writeValue(identifierType, identifier, value) {
138
- return this.getVariableRecord(identifierType, identifier).setValue(value);
139
- }
140
-
141
- collectDesiredEntries(treeConfig) {
142
- const desiredEntries = new Map();
143
- const objectTypeConfigs = this.buildObjectTypeConfigMap(treeConfig);
144
-
145
- (Array.isArray(treeConfig.objectsTypes) ? treeConfig.objectsTypes : []).forEach((objectTypeConfig) => {
146
- this.collectObjectTypeDefinition(desiredEntries, objectTypeConfig, objectTypeConfigs);
147
- });
148
-
149
- (Array.isArray(treeConfig.folders) ? treeConfig.folders : []).forEach((folderConfig) => {
150
- this.collectBranch(desiredEntries, "folder", folderConfig, "", "organizedBy", objectTypeConfigs);
151
- });
152
-
153
- (Array.isArray(treeConfig.objects) ? treeConfig.objects : []).forEach((objectConfig) => {
154
- this.collectBranch(desiredEntries, "object", objectConfig, "", "organizedBy", objectTypeConfigs);
155
- });
156
-
157
- return desiredEntries;
158
- }
159
-
160
- buildObjectTypeConfigMap(treeConfig) {
161
- const objectTypeConfigs = new Map();
162
-
163
- (Array.isArray(treeConfig.objectsTypes) ? treeConfig.objectsTypes : []).forEach((objectTypeConfig) => {
164
- if (objectTypeConfigs.has(objectTypeConfig.name)) {
165
- throw new Error("Duplicate object type name: " + objectTypeConfig.name);
166
- }
167
- objectTypeConfigs.set(objectTypeConfig.name, objectTypeConfig);
168
- });
169
-
170
- return objectTypeConfigs;
171
- }
172
-
173
- collectObjectTypeDefinition(desiredEntries, config, objectTypeConfigs) {
174
- const path = this.buildObjectTypePath(config.name);
175
- desiredEntries.set(path, this.buildEntryDefinition("objectTypeDefinition", config, path, "", "typeDefinition"));
176
- this.collectBranchChildren(desiredEntries, config, path, "componentOf", objectTypeConfigs, {
177
- skipAlarms: false,
178
- preserveCollectionNames: true,
179
- typeRootPath: path
180
- });
181
- }
182
-
183
- collectBranch(desiredEntries, kind, config, parentPath, relationship, objectTypeConfigs, options) {
184
- const settings = options || {};
185
- const path = settings.preserveCollectionNames
186
- ? this.buildCollectionPath(parentPath, kind === "folder" ? "folders" : "objects", config.name)
187
- : this.buildPath(parentPath, config.name);
188
- desiredEntries.set(path, this.buildEntryDefinition(kind, config, path, parentPath, relationship));
189
- this.collectBranchChildren(
190
- desiredEntries,
191
- config,
192
- path,
193
- kind === "folder" ? "organizedBy" : "componentOf",
194
- objectTypeConfigs,
195
- settings
196
- );
197
- }
198
-
199
- collectBranchChildren(desiredEntries, config, path, childRelationship, objectTypeConfigs, options) {
200
- const settings = options || {};
201
- const preserveCollectionNames = !!settings.preserveCollectionNames;
202
- (Array.isArray(config.folders) ? config.folders : []).forEach((folderConfig) => {
203
- this.collectBranch(
204
- desiredEntries,
205
- "folder",
206
- folderConfig,
207
- path,
208
- childRelationship,
209
- objectTypeConfigs,
210
- settings
211
- );
212
- });
213
-
214
- (Array.isArray(config.objects) ? config.objects : []).forEach((objectConfig) => {
215
- this.collectBranch(
216
- desiredEntries,
217
- "object",
218
- objectConfig,
219
- path,
220
- childRelationship,
221
- objectTypeConfigs,
222
- settings
223
- );
224
- });
225
-
226
- (Array.isArray(config.variables) ? config.variables : []).forEach((variableConfig) => {
227
- const childPath = preserveCollectionNames
228
- ? this.buildCollectionPath(path, "variables", variableConfig.name)
229
- : this.buildPath(path, variableConfig.name);
230
- desiredEntries.set(
231
- childPath,
232
- this.buildEntryDefinition("variable", variableConfig, childPath, path, "componentOf")
233
- );
234
- });
235
-
236
- (Array.isArray(config.methods) ? config.methods : []).forEach((methodConfig) => {
237
- const childPath = preserveCollectionNames
238
- ? this.buildCollectionPath(path, "methods", methodConfig.name)
239
- : this.buildPath(path, methodConfig.name);
240
- desiredEntries.set(
241
- childPath,
242
- this.buildEntryDefinition("method", methodConfig, childPath, path, "componentOf")
243
- );
244
- });
245
-
246
- (Array.isArray(config.objectsTypes) ? config.objectsTypes : []).forEach((objectTypeInstanceConfig) => {
247
- this.collectObjectTypeInstance(
248
- desiredEntries,
249
- objectTypeInstanceConfig,
250
- path,
251
- childRelationship,
252
- objectTypeConfigs,
253
- settings
254
- );
255
- });
256
-
257
- if (!settings.skipAlarms) {
258
- //alarmes
259
- (Array.isArray(config.alarms) ? config.alarms : []).forEach((alarmConfig) => {
260
- const childPath = preserveCollectionNames
261
- ? this.buildCollectionPath(path, "alarms", alarmConfig.name)
262
- : this.buildPath(path, alarmConfig.name);
263
-
264
- desiredEntries.set(
265
- childPath,
266
- this.buildEntryDefinition("alarm", alarmConfig, childPath, path, "componentOf")
267
- );
268
- //this.addAlarmPlaceholder(path, alarmConfig);
269
- });
270
- }
271
- }
272
-
273
- collectObjectTypeInstance(desiredEntries, instanceConfig, parentPath, relationship, objectTypeConfigs, options) {
274
- const path = this.buildPath(parentPath, instanceConfig.name);
275
- desiredEntries.set(
276
- path,
277
- this.buildEntryDefinition("objectTypeInstance", instanceConfig, path, parentPath, relationship)
278
- );
279
- // Variables, methods and nested objectsTypes inherited from the type definition
280
- // are instantiated automatically by node-opcua when addObject({ typeDefinition })
281
- // is called. We must NOT add them as independent desiredEntries — that would
282
- // create duplicate nodes on top of the ones the lib already created.
283
- // Only extra children defined directly on the instance (not inherited) are added here.
284
- const instanceOnlyConfig = {
285
- folders: Array.isArray(instanceConfig.folders) ? instanceConfig.folders : [],
286
- objects: Array.isArray(instanceConfig.objects) ? instanceConfig.objects : [],
287
- variables: Array.isArray(instanceConfig.variables) ? instanceConfig.variables : [],
288
- methods: Array.isArray(instanceConfig.methods) ? instanceConfig.methods : [],
289
- alarms: Array.isArray(instanceConfig.alarms) ? instanceConfig.alarms : [],
290
- objectsTypes: Array.isArray(instanceConfig.objectsTypes) ? instanceConfig.objectsTypes : []
291
- };
292
- this.collectBranchChildren(desiredEntries, instanceOnlyConfig, path, "componentOf", objectTypeConfigs, Object.assign({}, options, {
293
- skipAlarms: false,
294
- preserveCollectionNames: false
295
- }));
296
- }
297
-
298
- expandObjectTypeInstanceConfig(instanceConfig, objectTypeConfigs, stack) {
299
- const typeName = instanceConfig.objectsType;
300
- if (stack.indexOf(typeName) !== -1) {
301
- throw new Error("Circular object type reference: " + stack.concat(typeName).join(" -> "));
302
- }
303
-
304
- const baseConfig = objectTypeConfigs.get(typeName);
305
- if (!baseConfig) {
306
- throw new Error("Unknown object type: " + typeName);
307
- }
308
-
309
- return this.mergeObjectTypeConfigs(baseConfig, instanceConfig, objectTypeConfigs, stack.concat(typeName));
310
- }
311
-
312
- mergeObjectTypeConfigs(baseConfig, instanceConfig, objectTypeConfigs, stack) {
313
- const merged = {
314
- name: instanceConfig.name,
315
- displayName: instanceConfig.displayName || instanceConfig.name,
316
- description: instanceConfig.description || "",
317
- folders: this.cloneConfigs(baseConfig.folders),
318
- objects: this.cloneConfigs(baseConfig.objects),
319
- variables: this.cloneConfigs(baseConfig.variables),
320
- methods: this.cloneConfigs(baseConfig.methods),
321
- alarms: this.cloneConfigs(baseConfig.alarms),
322
- objectsTypes: []
323
- };
324
-
325
- const baseInstances = Array.isArray(baseConfig.objectsTypes) ? baseConfig.objectsTypes : [];
326
- const instanceOverrides = Array.isArray(instanceConfig.objectsTypes) ? instanceConfig.objectsTypes : [];
327
-
328
- merged.objectsTypes = baseInstances
329
- .map((nestedInstanceConfig) => this.mergeNestedObjectTypeInstance(nestedInstanceConfig, objectTypeConfigs, stack))
330
- .concat(this.cloneConfigs(instanceOverrides));
331
-
332
- merged.folders = merged.folders.concat(this.cloneConfigs(instanceConfig.folders));
333
- merged.objects = merged.objects.concat(this.cloneConfigs(instanceConfig.objects));
334
- merged.variables = merged.variables.concat(this.cloneConfigs(instanceConfig.variables));
335
- merged.methods = merged.methods.concat(this.cloneConfigs(instanceConfig.methods));
336
- merged.alarms = merged.alarms.concat(this.cloneConfigs(instanceConfig.alarms));
337
-
338
- return merged;
339
- }
340
-
341
- mergeNestedObjectTypeInstance(instanceConfig, objectTypeConfigs, stack) {
342
- const cloned = this.cloneConfig(instanceConfig);
343
- if (cloned && cloned.objectsType) {
344
- const typeName = cloned.objectsType;
345
- if (stack.indexOf(typeName) !== -1) {
346
- throw new Error("Circular object type reference: " + stack.concat(typeName).join(" -> "));
347
- }
348
- if (!objectTypeConfigs.has(typeName)) {
349
- throw new Error("Unknown object type: " + typeName);
350
- }
351
- }
352
- return cloned;
353
- }
354
-
355
- cloneConfigs(items) {
356
- return Array.isArray(items) ? items.map((item) => this.cloneConfig(item)) : [];
357
- }
358
-
359
- cloneConfig(item) {
360
- return item ? JSON.parse(JSON.stringify(item)) : item;
361
- }
362
-
363
- rewriteAlarmVariablePaths(branchConfig, currentPath, rootInstancePath) {
364
- if (!branchConfig || typeof branchConfig !== "object") {
365
- return;
366
- }
367
-
368
- (Array.isArray(branchConfig.alarms) ? branchConfig.alarms : []).forEach((alarmConfig) => {
369
- const variableNodeId = String(alarmConfig.variableNodeId || "").trim();
370
- if (!variableNodeId) {
371
- return;
372
- }
373
-
374
- if (variableNodeId.indexOf(".") === 0) {
375
- alarmConfig.variableNodeId = this.resolveObjectTypeRelativeReference(rootInstancePath, variableNodeId);
376
- return;
377
- }
378
-
379
- if (variableNodeId.indexOf(currentPath + ".") === 0 || variableNodeId === currentPath) {
380
- return;
381
- }
382
-
383
- if (variableNodeId.indexOf(rootInstancePath + ".") === 0 || variableNodeId === rootInstancePath) {
384
- return;
385
- }
386
-
387
- if (variableNodeId.indexOf("__objectTypes.") === 0) {
388
- return;
389
- }
390
-
391
- alarmConfig.variableNodeId = currentPath + "." + variableNodeId;
392
- });
393
-
394
- (Array.isArray(branchConfig.folders) ? branchConfig.folders : []).forEach((folderConfig) => {
395
- this.rewriteAlarmVariablePaths(folderConfig, this.buildPath(currentPath, folderConfig.name), rootInstancePath);
396
- });
397
-
398
- (Array.isArray(branchConfig.objects) ? branchConfig.objects : []).forEach((objectConfig) => {
399
- this.rewriteAlarmVariablePaths(objectConfig, this.buildPath(currentPath, objectConfig.name), rootInstancePath);
400
- });
401
- }
402
-
403
- buildEntryDefinition(kind, config, path, parentPath, relationship) {
404
- return {
405
- kind,
406
- path,
407
- parentPath,
408
- relationship,
409
- config,
410
- signature: this.buildSignature(kind, config, parentPath, relationship)
411
- };
412
- }
413
-
414
- buildSignature(kind, config, parentPath, relationship) {
415
- if (kind === "variable") {
416
- return JSON.stringify({
417
- kind,
418
- parentPath,
419
- relationship,
420
- nodeId: config.nodeId || "",
421
- namespaceId: this.resolveNamespaceId(config),
422
- displayName: config.displayName || config.name,
423
- description: config.description || "",
424
- type: config.type,
425
- access: config.access,
426
- isArray: this.isArrayValue(config.value)
427
- });
428
- }
429
-
430
- if (kind === "method") {
431
- return JSON.stringify({
432
- kind,
433
- parentPath,
434
- relationship,
435
- nodeId: config.nodeId || "",
436
- namespaceId: this.resolveNamespaceId(config),
437
- displayName: config.displayName || config.name,
438
- description: config.description || "",
439
- inputs: Array.isArray(config.inputs) ? config.inputs : [],
440
- outputs: Array.isArray(config.outputs) ? config.outputs : []
441
- });
442
- }
443
-
444
- if (kind === "objectTypeInstance") {
445
- return JSON.stringify({
446
- kind,
447
- parentPath,
448
- relationship,
449
- nodeId: config.nodeId || "",
450
- namespaceId: this.resolveNamespaceId(config),
451
- displayName: config.displayName || config.name,
452
- description: config.description || "",
453
- objectsType: config.objectsType
454
- });
455
- }
456
-
457
- return JSON.stringify({
458
- kind,
459
- parentPath,
460
- relationship,
461
- nodeId: config.nodeId || "",
462
- namespaceId: this.resolveNamespaceId(config),
463
- displayName: config.displayName || config.name,
464
- description: config.description || ""
465
- });
466
- }
467
-
468
- collectRemovalRoots(desiredEntries) {
469
- const candidates = [];
470
-
471
- this.nodeEntries.forEach((entry, path) => {
472
- const desired = desiredEntries.get(path);
473
-
474
- if (!desired) {
475
- candidates.push(path);
476
- return;
477
- }
478
-
479
- if (entry.kind !== desired.kind || entry.signature !== desired.signature) {
480
- candidates.push(path);
481
- }
482
- });
483
-
484
- return collapsePaths(candidates);
485
- }
486
-
487
- updateExistingNodes(desiredEntries) {
488
- desiredEntries.forEach((desired, path) => {
489
- const existing = this.nodeEntries.get(path);
490
- if (!existing || existing.kind !== desired.kind) {
491
- return;
492
- }
493
-
494
- if (existing.kind === "variable") {
495
- const record = this.variableStore.get(path);
496
- if (record) {
497
- record.setRuntimeValue(desired.config.value);
498
- }
499
- }
500
- });
501
- }
502
-
503
- createMissingNodes(desiredEntries) {
504
- const ordered = Array.from(desiredEntries.values()).sort(compareEntryCreationOrder);
505
- ordered.forEach((definition) => {
506
- if (this.nodeEntries.has(definition.path)) {
507
- return;
508
- }
509
-
510
- this.createNode(definition);
511
- });
512
- }
513
-
514
- createNode(definition) {
515
- const parentNode = this.resolveParentNode(definition.parentPath);
516
-
517
-
518
-
519
- if (definition.kind === "folder") {
520
- this.addFolder(parentNode, definition.config, definition.parentPath, definition.relationship, definition.path);
521
- return;
522
- }
523
-
524
- if (definition.kind === "objectTypeDefinition") {
525
-
526
- this.addObjectTypeDefinition(definition.config);
527
- return;
528
- }
529
-
530
- if (definition.kind === "object") {
531
- this.addObject(parentNode, definition.config, definition.parentPath, definition.relationship, definition.path);
532
- return;
533
- }
534
-
535
- if (definition.kind === "objectTypeInstance") {
536
- this.addObjectTypeInstance(parentNode, definition.config, definition.parentPath, definition.relationship, definition.path);
537
- return;
538
- }
539
-
540
- if (definition.kind === "alarm") {
541
- this.addAlarm(parentNode, definition.config, definition.parentPath, definition.relationship, definition.path);
542
- return;
543
-
544
- }
545
-
546
- if (definition.kind === "variable") {
547
- this.addVariable(parentNode, definition.config, definition.parentPath, definition.path);
548
- return;
549
- }
550
-
551
- if (definition.kind === "method") {
552
- this.addMethod(parentNode, definition.config, definition.parentPath, definition.path);
553
- }
554
-
555
-
556
- }
557
-
558
- resolveParentNode(parentPath) {
559
- if (!parentPath) {
560
- return this.server.engine.addressSpace.rootFolder.objects;
561
- }
562
-
563
- const parentEntry = this.nodeEntries.get(parentPath);
564
- if (!parentEntry || !parentEntry.node) {
565
- throw new Error("Parent node is not available for path: " + parentPath);
566
- }
567
-
568
- return parentEntry.node;
569
- }
570
-
571
- removeSubtree(rootPath) {
572
- const affectedPaths = Array.from(this.nodeEntries.keys())
573
- .filter((path) => isSamePathOrDescendant(path, rootPath))
574
- .sort(comparePathDepthDesc);
575
-
576
- affectedPaths.forEach((path) => this.removeSingleNode(path));
577
- }
578
-
579
- removeSingleNode(path) {
580
- const entry = this.nodeEntries.get(path);
581
- if (!entry) {
582
- return;
583
- }
584
-
585
- if (entry.kind === "variable") {
586
- const record = this.variableStore.get(path);
587
- if (record && record.nodeIdKey) {
588
- this.variableNodeIdStore.delete(record.nodeIdKey);
589
- }
590
- this.variableStore.delete(path);
591
- }
592
-
593
- if (entry.kind === "objectTypeDefinition") {
594
- this.objectTypeStore.delete(entry.config.name);
595
- }
596
-
597
- try {
598
- entry.namespace.deleteNode(entry.node);
599
-
600
- } catch (error) {
601
- console.error("error removeSingleNode")
602
- console.error(error)
603
- this.node.debug("Ignoring deleteNode error for " + path + ": " + error.message);
604
- }
605
-
606
-
607
-
608
- this.nodeEntries.delete(path);
609
- }
610
-
611
- addObject(parentNode, objectConfig, parentPath, relationship, pathOverride) {
612
- const namespace = this.getNamespaceForConfig(objectConfig);
613
-
614
- const addressSpace = this.server.engine.addressSpace;
615
- const serverNode = addressSpace.rootFolder.objects.server;
616
-
617
-
618
- const objectName = objectConfig.name;
619
- const nextPath = pathOverride || this.buildPath(parentPath, objectName);
620
- const options = {
621
- browseName: objectConfig.displayName || objectName,
622
- displayName: objectConfig.displayName || objectName,
623
- description: objectConfig.description || "",
624
- nodeId: this.resolveNodeId(objectConfig, nextPath, namespace),
625
- eventNotifier: 1, //enabled_events,
626
- eventSourceOf: serverNode
627
- };
628
-
629
- if (relationship === "organizedBy") {
630
- options.organizedBy = parentNode;
631
- } else {
632
- options.componentOf = parentNode;
633
- }
634
-
635
- if (this.isObjectTypePath(parentPath)) {
636
- options.modellingRule = "Mandatory";
637
- }
638
-
639
- const objectNode = namespace.addObject(options);
640
- this.registerNodeEntry("object", nextPath, parentPath, relationship, objectConfig, objectNode, namespace);
641
- }
642
-
643
- addObjectTypeDefinition(objectTypeConfig) {
644
- const namespace = this.getNamespaceForConfig(objectTypeConfig);
645
- const objectTypeNode = namespace.addObjectType({
646
- browseName: objectTypeConfig.name,
647
- displayName: objectTypeConfig.displayName || objectTypeConfig.name,
648
- description: objectTypeConfig.description || "",
649
- nodeId: this.resolveNodeId(objectTypeConfig, this.buildObjectTypePath(objectTypeConfig.name), namespace),
650
- subtypeOf: "BaseObjectType"
651
- });
652
-
653
-
654
- const path = this.buildObjectTypePath(objectTypeConfig.name);
655
-
656
-
657
-
658
- this.registerNodeEntry("objectTypeDefinition", path, "", "typeDefinition", objectTypeConfig, objectTypeNode, namespace);
659
- this.objectTypeStore.set(objectTypeConfig.name, {
660
- node: objectTypeNode,
661
- config: objectTypeConfig,
662
- path: path,
663
- namespace: namespace
664
- });
665
- }
666
-
667
- addObjectTypeInstance(parentNode, instanceConfig, parentPath, relationship, pathOverride) {
668
- const objectTypeEntry = this.objectTypeStore.get(instanceConfig.objectsType);
669
- if (!objectTypeEntry || !objectTypeEntry.node) {
670
- throw new Error("Object type is not available for instance " + instanceConfig.name + ": " + instanceConfig.objectsType);
671
- }
672
-
673
- const objectName = instanceConfig.name;
674
- const nextPath = pathOverride || this.buildPath(parentPath, objectName);
675
- const namespace = this.getNamespaceForConfig(instanceConfig);
676
-
677
- const options = {
678
- browseName: instanceConfig.displayName || objectName,
679
- displayName: instanceConfig.displayName || objectName,
680
- description: instanceConfig.description || "",
681
- nodeId: this.resolveNodeId(instanceConfig, nextPath, namespace),
682
- typeDefinition: objectTypeEntry.node.nodeId,
683
- eventNotifier: 1
684
- };
685
-
686
- if (relationship === "organizedBy") {
687
- options.organizedBy = parentNode;
688
- } else {
689
- options.componentOf = parentNode;
690
- }
691
-
692
- const objectNode = namespace.addObject(options);
693
- this.registerNodeEntry("objectTypeInstance", nextPath, parentPath, relationship, instanceConfig, objectNode, namespace);
694
-
695
- // Create the inherited children explicitly using the configs that opcua-config.js
696
- // already rewrote with the correct instance nodeIds (e.g. server1.newObjectType.cmd_on).
697
- // We do NOT call instantiate() because node-opcua cannot auto-assign nodeIds for the
698
- // cloned children when the type children already have explicit string nodeIds registered.
699
- this.createInheritedChildren(objectNode, nextPath, instanceConfig, namespace);
700
- }
701
-
702
- createInheritedChildren(instanceNode, instancePath, instanceConfig, namespace) {
703
- const typeEntry = this.objectTypeStore.get(instanceConfig.objectsType);
704
- if (!typeEntry) return;
705
-
706
- // Extract the type's nodeId value prefix (e.g. "Motor_type2") and the
707
- // instance's nodeId value prefix (e.g. "server1.newObjectType") so we can
708
- // rewrite every child nodeId from the type to the instance on-the-fly.
709
- const typeNodeId = typeEntry.config.nodeId || "";
710
- const instanceNodeId = instanceConfig.nodeId || "";
711
- const typePrefix = this.extractNodeIdStringValue(typeNodeId);
712
- const instancePrefix = this.extractNodeIdStringValue(instanceNodeId);
713
-
714
- this.createInheritedBranchChildren(typeEntry.config, instanceNode, instancePath, typePrefix, instancePrefix);
715
- }
716
-
717
- extractNodeIdStringValue(nodeId) {
718
- const m = String(nodeId || "").match(/(?:^|;)s=(.+)$/);
719
- return m ? m[1] : "";
720
- }
721
-
722
- rewriteInheritedNodeId(nodeId, typePrefix, instancePrefix) {
723
- if (!nodeId || !typePrefix || !instancePrefix) return nodeId;
724
- const m = String(nodeId).match(/^(ns=\d+;s=)([\s\S]*)$/);
725
- if (m && m[2].startsWith(typePrefix)) {
726
- return m[1] + instancePrefix + m[2].slice(typePrefix.length);
727
- }
728
- return nodeId;
729
- }
730
-
731
- createInheritedBranchChildren(typeConfig, parentOpcNode, parentPath, typePrefix, instancePrefix) {
732
- const variables = Array.isArray(typeConfig.variables) ? typeConfig.variables : [];
733
- const methods = Array.isArray(typeConfig.methods) ? typeConfig.methods : [];
734
- const folders = Array.isArray(typeConfig.folders) ? typeConfig.folders : [];
735
- const objects = Array.isArray(typeConfig.objects) ? typeConfig.objects : [];
736
-
737
- variables.forEach((varConfig) => {
738
- const childPath = this.buildPath(parentPath, varConfig.name);
739
- const rewrittenConfig = Object.assign({}, varConfig, {
740
- nodeId: this.rewriteInheritedNodeId(varConfig.nodeId, typePrefix, instancePrefix)
741
- });
742
- this.addVariable(parentOpcNode, rewrittenConfig, parentPath, childPath);
743
- });
744
-
745
- methods.forEach((methodConfig) => {
746
- const childPath = this.buildPath(parentPath, methodConfig.name);
747
- const rewrittenConfig = Object.assign({}, methodConfig, {
748
- nodeId: this.rewriteInheritedNodeId(methodConfig.nodeId, typePrefix, instancePrefix)
749
- });
750
- this.addMethod(parentOpcNode, rewrittenConfig, parentPath, childPath);
751
- });
752
-
753
- folders.forEach((folderConfig) => {
754
- const childPath = this.buildPath(parentPath, folderConfig.name);
755
- const rewrittenConfig = Object.assign({}, folderConfig, {
756
- nodeId: this.rewriteInheritedNodeId(folderConfig.nodeId, typePrefix, instancePrefix)
757
- });
758
- this.addFolder(parentOpcNode, rewrittenConfig, parentPath, "componentOf", childPath);
759
- const childNode = this.nodeEntries.get(childPath) && this.nodeEntries.get(childPath).node;
760
- if (childNode) {
761
- this.createInheritedBranchChildren(folderConfig, childNode, childPath, typePrefix, instancePrefix);
762
- }
763
- });
764
-
765
- objects.forEach((objectConfig) => {
766
- const childPath = this.buildPath(parentPath, objectConfig.name);
767
- const rewrittenConfig = Object.assign({}, objectConfig, {
768
- nodeId: this.rewriteInheritedNodeId(objectConfig.nodeId, typePrefix, instancePrefix)
769
- });
770
- this.addObject(parentOpcNode, rewrittenConfig, parentPath, "componentOf", childPath);
771
- const childNode = this.nodeEntries.get(childPath) && this.nodeEntries.get(childPath).node;
772
- if (childNode) {
773
- this.createInheritedBranchChildren(objectConfig, childNode, childPath, typePrefix, instancePrefix);
774
- }
775
- });
776
- }
777
-
778
- addAlarm(parentNode, alarmConfig, parentPath, relationship, pathOverride) {
779
-
780
-
781
-
782
- try {
783
-
784
- const name = alarmConfig.name;
785
- const variableNodeId = alarmConfig.variableNodeId;
786
- const objectName = alarmConfig.name;
787
- const nextPath = pathOverride || this.buildPath(parentPath, objectName);
788
- const path = pathOverride || this.buildPath(parentPath, name);
789
- const namespace = this.getNamespaceForConfig(alarmConfig);
790
- const options = {
791
- browseName: alarmConfig.displayName || objectName,
792
- displayName: alarmConfig.displayName || objectName,
793
- description: alarmConfig.description || "",
794
- nodeId: this.resolveNodeId(alarmConfig, nextPath, namespace),
795
- eventNotifier: 1 //enabled_events
796
- };
797
-
798
- if (relationship === "organizedBy") {
799
- options.organizedBy = parentNode;
800
- } else {
801
- options.componentOf = parentNode;
802
- }
803
-
804
- const variableToMonitor = this.resolveAlarmVariableRecord(parentPath, parentNode, variableNodeId)
805
-
806
- const sourceName = variableToMonitor.node.displayName[0].text
807
-
808
- const browseName = alarmConfig.displayName || objectName
809
- const inputNode = variableToMonitor.node
810
-
811
-
812
-
813
- const conditionName = alarmConfig.displayName || objectName
814
- const nodeId = this.resolveNodeId(alarmConfig, nextPath, namespace)
815
-
816
- const alarmNode = this.addressSpaceAlarm.createAlarm(namespace, browseName, parentNode, inputNode, conditionName, nodeId, sourceName, alarmConfig)
817
-
818
-
819
-
820
-
821
- // register alarm
822
- variableToMonitor.alarm = {
823
- node: alarmNode,
824
- alarmConfig: alarmConfig,
825
- }
826
- // const alarmNode = this.namespace.instantiateOffNormalAlarm(options);
827
- this.registerNodeEntry("alarm", nextPath, parentPath, relationship, alarmConfig, alarmNode, namespace);
828
-
829
-
830
-
831
-
832
- } catch (error) {
833
-
834
- if (error && error.message && error.message.indexOf("Unknown alarm variable reference:") === 0) {
835
- this.queuePendingAlarm(parentNode, alarmConfig, parentPath, relationship);
836
- return;
837
- }
838
- console.error("erro addAlarm")
839
- console.error(error)
840
- }
841
-
842
-
843
- }
844
-
845
- queuePendingAlarm(parentNode, alarmConfig, parentPath, relationship) {
846
- const path = this.buildPath(parentPath, alarmConfig.name);
847
- const alreadyQueued = this.pendingAlarms.some((item) => item.path === path);
848
- if (alreadyQueued) {
849
- return;
850
- }
851
-
852
- this.pendingAlarms.push({
853
- path: path,
854
- parentNode: parentNode,
855
- alarmConfig: this.cloneConfig(alarmConfig),
856
- parentPath: parentPath,
857
- relationship: relationship
858
- });
859
- }
860
-
861
- flushPendingAlarms() {
862
- if (!this.pendingAlarms.length) {
863
- return;
864
- }
865
-
866
- const pending = this.pendingAlarms.slice();
867
- this.pendingAlarms = [];
868
-
869
- pending.forEach((item) => {
870
- if (this.nodeEntries.has(item.path)) {
871
- return;
872
- }
873
-
874
- this.addAlarm(item.parentNode, item.alarmConfig, item.parentPath, item.relationship);
875
- });
876
-
877
- if (this.pendingAlarms.length) {
878
- const unresolved = this.pendingAlarms.map((item) => item.path).join(", ");
879
- this.pendingAlarms = [];
880
- throw new Error("Unresolved alarms after build: " + unresolved);
881
- }
882
- }
883
-
884
-
885
-
886
-
887
- addFolder(parentNode, folderConfig, parentPath, relationship, pathOverride) {
888
- const namespace = this.getNamespaceForConfig(folderConfig);
889
-
890
- const addressSpace = this.server.engine.addressSpace;
891
- const serverNode = addressSpace.rootFolder.objects.server;
892
-
893
- const folderName = folderConfig.name;
894
- const nextPath = pathOverride || this.buildPath(parentPath, folderName);
895
- const options = {
896
- browseName: folderConfig.displayName || folderName,
897
- displayName: folderConfig.displayName || folderName,
898
- description: folderConfig.description || "",
899
- nodeId: this.resolveNodeId(folderConfig, nextPath, namespace),
900
- typeDefinition: "FolderType",
901
- eventSourceOf: serverNode,
902
- eventNotifier: 1 //enabled_events
903
- };
904
-
905
- if (relationship === "organizedBy") {
906
- options.organizedBy = parentNode;
907
- } else {
908
- options.componentOf = parentNode;
909
- }
910
-
911
- if (this.isObjectTypePath(parentPath)) {
912
- options.modellingRule = "Mandatory";
913
- }
914
-
915
- const folderNode = namespace.addObject(options);
916
- this.registerNodeEntry("folder", nextPath, parentPath, relationship, folderConfig, folderNode, namespace);
917
- }
918
-
919
- addVariable(parentNode, variableConfig, parentPath, pathOverride) {
920
- const namespace = this.getNamespaceForConfig(variableConfig);
921
- const name = variableConfig.name;
922
- const type = variableConfig.type;
923
- const access = variableConfig.access;
924
- const path = pathOverride || this.buildPath(parentPath, name);
925
- const nodeId = this.resolveNodeId(variableConfig, path, namespace);
926
- const browseName = variableConfig.displayName || name;
927
- const state = {
928
- type,
929
- access,
930
- isArray: this.isArrayValue(variableConfig.value),
931
- currentValue: variableConfig.value
932
- };
933
-
934
- const variableNode = namespace.addVariable({
935
- componentOf: parentNode,
936
- browseName,
937
- displayName: browseName,
938
- description: variableConfig.description || "",
939
- nodeId,
940
- dataType: type,
941
- modellingRule: this.isObjectTypePath(parentPath) ? "Mandatory" : undefined,
942
- valueRank: state.isArray ? 1 : -1,
943
- accessLevel: access === "readwrite" ? "CurrentRead | CurrentWrite" : "CurrentRead",
944
- userAccessLevel: access === "readwrite" ? "CurrentRead | CurrentWrite" : "CurrentRead",
945
- minimumSamplingInterval: 500,
946
- value: {
947
- get: () => {
948
- this.emitTagAccess("read", {
949
- path,
950
- nodeID: nodeId,
951
- browseName,
952
- dataType: state.type,
953
- value: state.currentValue
954
- });
955
-
956
- const variantOptions = {
957
- dataType: DATA_TYPE_MAP[state.type],
958
- value: state.currentValue
959
- };
960
-
961
- // ByteString nunca e array - Buffer nao deve ser VariantArrayType.Array
962
- if (state.type !== "ByteString" && this.isArrayValue(state.currentValue)) {
963
- variantOptions.arrayType = VariantArrayType.Array;
964
- }
965
-
966
- return new Variant(variantOptions);
967
- },
968
- set: (variant) => {
969
- if (state.access !== "readwrite") {
970
- return StatusCodes.BadNotWritable;
971
- }
972
-
973
- try {
974
- state.currentValue = this.coerceValue(variant.value, state.type, state.isArray);
975
- this.emitTagAccess("write", {
976
- path,
977
- nodeID: nodeId,
978
- browseName,
979
- dataType: state.type,
980
- value: state.currentValue
981
- });
982
-
983
- const alarm = this.variableStore.get(path).alarm
984
- this.addressSpaceAlarm.checkAlarm(alarm, variant.value)
985
-
986
-
987
-
988
- return StatusCodes.Good;
989
- } catch (error) {
990
- console.error("addVariable")
991
- console.error(error)
992
- // this.node.warn("Rejected OPC UA write for " + path + ": " + error.message);
993
- return StatusCodes.BadTypeMismatch;
994
- }
995
- }
996
- }
997
- });
998
-
999
- this.registerNodeEntry("variable", path, parentPath, "componentOf", variableConfig, variableNode, namespace);
1000
- const record = {
1001
- node: variableNode,
1002
- path: path,
1003
- nodeId: nodeId,
1004
- nodeIdKey: this.normalizeNodeIdKey(nodeId),
1005
- getValue: () => state.currentValue,
1006
- setRuntimeValue: (nextValue) => {
1007
- state.currentValue = this.coerceValue(nextValue, state.type, state.isArray);
1008
- return state.currentValue;
1009
- },
1010
- setValue: (nextValue) => {
1011
- if (state.access !== "readwrite") {
1012
- throw new Error("Tag is read-only: " + path);
1013
- }
1014
-
1015
- state.currentValue = this.coerceValue(nextValue, state.type, state.isArray);
1016
-
1017
- const alarm = this.variableStore.get(path).alarm
1018
- this.addressSpaceAlarm.checkAlarm(alarm, state.currentValue)
1019
-
1020
- return state.currentValue;
1021
- }
1022
- };
1023
- this.variableStore.set(path, record);
1024
- this.variableNodeIdStore.set(record.nodeIdKey, record);
1025
- }
1026
-
1027
-
1028
-
1029
-
1030
-
1031
- addMethod(parentNode, methodConfig, parentPath, pathOverride) {
1032
- const namespace = this.getNamespaceForConfig(methodConfig);
1033
- const methodName = methodConfig.name;
1034
- const path = pathOverride || this.buildPath(parentPath, methodName);
1035
- const nodeId = this.resolveNodeId(methodConfig, path, namespace)
1036
- const methodNode = namespace.addMethod(parentNode, {
1037
- browseName: methodConfig.displayName || methodName,
1038
- displayName: methodConfig.displayName || methodName,
1039
- description: { text: methodConfig.description || "" },
1040
- nodeId: nodeId,
1041
- modellingRule: this.isObjectTypePath(parentPath) ? "Mandatory" : undefined,
1042
- inputArguments: methodConfig.inputs.map((arg) => ({
1043
- name: arg.name,
1044
- description: { text: arg.description || "" },
1045
- dataType: DATA_TYPE_MAP[arg.type]
1046
- })),
1047
- outputArguments: methodConfig.outputs.map((arg) => ({
1048
- name: arg.name,
1049
- description: { text: arg.description || "" },
1050
- dataType: DATA_TYPE_MAP[arg.type]
1051
- }))
1052
- });
1053
-
1054
- methodNode.bindMethod((inputArguments, context, callback) => {
1055
- const callId = Date.now() + "_" + Math.random();
1056
-
1057
- this.registry.emitMethodCall({
1058
- methodName: methodConfig.name,
1059
- nodeId: nodeId,
1060
- callId,
1061
- inputArguments,
1062
- outputArguments: methodConfig.outputs.map((arg) => ({
1063
- name: arg.name,
1064
- description: { text: arg.description || "" },
1065
- dataType: DATA_TYPE_MAP[arg.type]
1066
- })),
1067
- serverName: this.serverName
1068
- });
1069
-
1070
- this.registry.waitForMethodResponse(callId)
1071
- .then((outputs) => {
1072
- callback(null, {
1073
- statusCode: StatusCodes.Good,
1074
- outputArguments: outputs.map((output) => new Variant(output))
1075
- });
1076
- })
1077
- .catch(() => {
1078
- callback(null, {
1079
- statusCode: StatusCodes.BadInternalError
1080
- });
1081
- });
1082
- });
1083
-
1084
- this.registerNodeEntry("method", path, parentPath, "componentOf", methodConfig, methodNode, namespace);
1085
- }
1086
-
1087
- addAlarmPlaceholder(parentPath, alarmConfig) {
1088
- const path = this.buildPath(parentPath, alarmConfig.name);
1089
- this.node.debug("Alarm definition received but not implemented yet: " + path + " (" + alarmConfig.type + ")");
1090
- }
1091
-
1092
- registerNodeEntry(kind, path, parentPath, relationship, config, node, namespace) {
1093
- this.nodeEntries.set(path, {
1094
- kind,
1095
- path,
1096
- parentPath,
1097
- relationship,
1098
- config,
1099
- signature: this.buildSignature(kind, config, parentPath, relationship),
1100
- node,
1101
- namespace
1102
- });
1103
- }
1104
-
1105
- buildPath(parentPath, name) {
1106
- return parentPath ? parentPath + "." + name : name;
1107
- }
1108
-
1109
- buildObjectTypePath(name) {
1110
- return "__objectTypes." + name;
1111
- }
1112
-
1113
- buildCollectionPath(parentPath, collectionName, name) {
1114
-
1115
- // return parentPath ? parentPath + "." + collectionName + "." + name : collectionName + "." + name;
1116
- //change nodeId path
1117
- return parentPath ? parentPath + "." + name : name + "." + name;
1118
-
1119
- }
1120
-
1121
- isObjectTypePath(path) {
1122
- return String(path || "").indexOf("__objectTypes.") === 0;
1123
- }
1124
-
1125
- buildStableNodeId(path, namespace) {
1126
- return "ns=" + namespace.index + ";s=" + sanitizeNodeIdPath(path);
1127
- }
1128
-
1129
- resolveNodeId(config, path, namespace) {
1130
- const customNodeId = config && typeof config.nodeId === "string" ? config.nodeId.trim() : "";
1131
- return customNodeId || this.buildStableNodeId(path, namespace);
1132
- }
1133
-
1134
- resolveNamespaceId(config) {
1135
- const namespaceId = config && config.namespaceId !== undefined ? Number(config.namespaceId) : 2;
1136
- return Number.isInteger(namespaceId) && namespaceId >= 2 ? namespaceId : 2;
1137
- }
1138
-
1139
- getNamespaceForConfig(config) {
1140
- const namespaceId = this.resolveNamespaceId(config);
1141
- if (!this.namespaces.has(namespaceId)) {
1142
- throw new Error("Namespace " + namespaceId + " is not available");
1143
- }
1144
-
1145
- return this.namespaces.get(namespaceId);
1146
- }
1147
-
1148
- emitTagAccess(operation, details) {
1149
- this.registry.emitTagAccess({
1150
- operation,
1151
- serverId: this.node.id,
1152
- serverNodeName: this.node.name || "",
1153
- serverName: this.serverName,
1154
- timestamp: new Date().toISOString(),
1155
- path: details.path,
1156
- nodeID: details.nodeID,
1157
- browseName: details.browseName,
1158
- dataType: details.dataType,
1159
- value: details.value
1160
- });
1161
- }
1162
-
1163
- getVariableRecord(identifierType, identifier) {
1164
- if (identifierType === "nodeId") {
1165
- return this.getVariableRecordByNodeId(identifier);
1166
- }
1167
-
1168
- return this.getVariableRecordByPath(identifier);
1169
- }
1170
-
1171
- getVariableRecordByPath(path) {
1172
- if (!this.variableStore.has(path)) {
1173
- throw new Error("Unknown path: " + path);
1174
- }
1175
-
1176
- return this.variableStore.get(path);
1177
- }
1178
-
1179
- getVariableRecordByNodeId(nodeId) {
1180
- const key = this.normalizeNodeIdKey(nodeId);
1181
- if (!this.variableNodeIdStore.has(key)) {
1182
- throw new Error("Unknown nodeId: " + nodeId);
1183
- }
1184
-
1185
- return this.variableNodeIdStore.get(key);
1186
- }
1187
-
1188
- normalizeNodeIdKey(nodeId) {
1189
- return coerceNodeId(nodeId).toString();
1190
- }
1191
-
1192
- resolveAlarmVariableRecord(parentPath, parentNode, variableNodeId) {
1193
- const reference = String(variableNodeId || "").trim();
1194
- if (!reference) {
1195
- throw new Error("Alarm variableNodeId is required");
1196
- }
1197
-
1198
- if (reference.indexOf(".") === 0) {
1199
- const relativeReference = this.resolveObjectTypeRelativeReference(parentPath, reference);
1200
-
1201
- //not work
1202
- var corrente = parentNode.getComponentByName("corrent")
1203
-
1204
- return {
1205
- node: corrente
1206
- }
1207
- if (this.variableStore.has(relativeReference)) {
1208
- return this.variableStore.get(relativeReference);
1209
- }
1210
- }
1211
-
1212
- if (this.variableStore.has(reference)) {
1213
- return this.variableStore.get(reference);
1214
- }
1215
-
1216
- try {
1217
- return this.getVariableRecordByNodeId(reference);
1218
- } catch (error) {
1219
- // ignore and continue with path heuristics
1220
- }
1221
-
1222
- const stringNodeIdPath = this.extractStringNodeIdPath(reference);
1223
- if (stringNodeIdPath) {
1224
- if (this.variableStore.has(stringNodeIdPath)) {
1225
- return this.variableStore.get(stringNodeIdPath);
1226
- }
1227
-
1228
- const relativeStringNodeIdPath = this.buildPath(parentPath, stringNodeIdPath);
1229
- if (this.variableStore.has(relativeStringNodeIdPath)) {
1230
- return this.variableStore.get(relativeStringNodeIdPath);
1231
- }
1232
- }
1233
-
1234
- const relativePath = this.buildPath(parentPath, reference);
1235
- if (this.variableStore.has(relativePath)) {
1236
- return this.variableStore.get(relativePath);
1237
- }
1238
-
1239
- const suffixMatches = Array.from(this.variableStore.entries())
1240
- .filter(([path]) => {
1241
- return path === reference ||
1242
- path.indexOf("." + reference) !== -1 ||
1243
- (stringNodeIdPath && (path === stringNodeIdPath || path.indexOf("." + stringNodeIdPath) !== -1));
1244
- });
1245
-
1246
- if (suffixMatches.length === 1) {
1247
- return suffixMatches[0][1];
1248
- }
1249
-
1250
- throw new Error("Unknown alarm variable reference: " + reference);
1251
- }
1252
-
1253
- extractStringNodeIdPath(reference) {
1254
- const match = /^ns=\d+;s=(.+)$/.exec(String(reference || "").trim());
1255
- return match ? match[1] : "";
1256
- }
1257
-
1258
- resolveObjectTypeRelativeReference(rootInstancePath, reference) {
1259
- const tokens = String(reference || "")
1260
- .trim()
1261
- .replace(/^\.+/, "")
1262
- .split(".")
1263
- .filter((token) => token !== "")
1264
- .map((token) => this.normalizeCollectionToken(token));
1265
-
1266
- if (!tokens.length) {
1267
- return rootInstancePath;
1268
- }
1269
-
1270
- return rootInstancePath + "." + tokens.join(".");
1271
- }
1272
-
1273
- normalizeCollectionToken(token) {
1274
- const normalized = String(token || "").trim().toLowerCase();
1275
- const aliases = {
1276
- variaveis: "variables",
1277
- variables: "variables",
1278
- objetos: "objects",
1279
- objects: "objects",
1280
- pastas: "folders",
1281
- folders: "folders",
1282
- metodos: "methods",
1283
- methods: "methods",
1284
- alarmes: "alarms",
1285
- alarms: "alarms",
1286
- objecttypes: "objectsTypes",
1287
- objectstypes: "objectsTypes",
1288
- tiposobjetos: "objectsTypes"
1289
- };
1290
-
1291
- return aliases[normalized] || token;
1292
- }
1293
-
1294
- coerceValue(value, type, expectArray) {
1295
- // ByteString nunca e array - Buffer/Uint8Array tratados diretamente
1296
- if (type === "ByteString") {
1297
- return this.coerceScalarValue(value, type);
1298
- }
1299
-
1300
- if (expectArray) {
1301
- const items = this.extractArrayItems(value);
1302
- if (!items) {
1303
- throw new Error("Expected array value for type " + type);
1304
- }
1305
-
1306
- return items.map((item) => this.coerceScalarValue(item, type));
1307
- }
1308
-
1309
- if (typeof value === "string") {
1310
- const trimmed = value.trim();
1311
- if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
1312
- try {
1313
- const parsed = JSON.parse(trimmed);
1314
- if (Array.isArray(parsed)) {
1315
- throw new Error("Expected scalar value for type " + type + " but received array");
1316
- }
1317
- } catch (error) {
1318
- if (error.message.indexOf("Expected scalar value") === 0) {
1319
- throw error;
1320
- }
1321
-
1322
- throw new Error("Invalid array value for type " + type + ": " + error.message);
1323
- }
1324
- }
1325
- }
1326
-
1327
- if (this.extractArrayItems(value)) {
1328
- throw new Error("Expected scalar value for type " + type + " but received array");
1329
- }
1330
-
1331
- return this.coerceScalarValue(value, type);
1332
- }
1333
-
1334
- coerceScalarValue(value, type) {
1335
-
1336
- if (type === "Int16") {
1337
- const parsed = Number(value);
1338
- if (!Number.isFinite(parsed)) {
1339
- return 0;
1340
- }
1341
- return Math.trunc(parsed);
1342
- }
1343
-
1344
-
1345
- if (type === "Int32") {
1346
- const parsed = Number(value);
1347
- if (!Number.isFinite(parsed)) {
1348
- return 0;
1349
- }
1350
- return Math.trunc(parsed);
1351
- }
1352
-
1353
- if (type === "Float") {
1354
- const parsed = Number(value);
1355
- if (!Number.isFinite(parsed)) {
1356
- return 0;
1357
- }
1358
- return parsed;
1359
- }
1360
-
1361
- if (type === "Boolean") {
1362
- if (typeof value === "string") {
1363
- const normalized = value.trim().toLowerCase();
1364
- if (normalized === "false" || normalized === "0" || normalized === "") {
1365
- return false;
1366
- }
1367
- if (normalized === "true" || normalized === "1") {
1368
- return true;
1369
- }
1370
- }
1371
-
1372
- return Boolean(value);
1373
- }
1374
-
1375
- if (type === "ByteString") {
1376
- if (Buffer.isBuffer(value)) {
1377
- return value;
1378
- }
1379
- if (value instanceof Uint8Array) {
1380
- return Buffer.from(value);
1381
- }
1382
- if (typeof value === "string") {
1383
- return Buffer.from(value, "base64");
1384
- }
1385
- if (Array.isArray(value)) {
1386
- return Buffer.from(value);
1387
- }
1388
- return Buffer.alloc(0);
1389
- }
1390
-
1391
- return value === undefined || value === null ? "" : String(value);
1392
- }
1393
-
1394
- isArrayValue(value) {
1395
- return this.extractArrayItems(value) !== null;
1396
- }
1397
-
1398
- extractArrayItems(value) {
1399
- if (Array.isArray(value)) {
1400
- return value;
1401
- }
1402
-
1403
- if (typeof value === "string") {
1404
- const trimmed = value.trim();
1405
- if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
1406
- const parsed = JSON.parse(trimmed);
1407
- return Array.isArray(parsed) ? parsed : null;
1408
- }
1409
- return null;
1410
- }
1411
-
1412
- if (ArrayBuffer.isView(value)) {
1413
- return Array.from(value);
1414
- }
1415
-
1416
- return null;
1417
- }
1418
- }
1419
-
1420
- function collapsePaths(paths) {
1421
- return Array.from(new Set(paths))
1422
- .sort(comparePathDepthAsc)
1423
- .filter((path, index, ordered) => {
1424
- for (let current = 0; current < index; current += 1) {
1425
- if (isSamePathOrDescendant(path, ordered[current])) {
1426
- return false;
1427
- }
1428
- }
1429
- return true;
1430
- });
1431
- }
1432
-
1433
- function isSamePathOrDescendant(path, rootPath) {
1434
- return path === rootPath || path.indexOf(rootPath + ".") === 0;
1435
- }
1436
-
1437
- function comparePathDepthAsc(left, right) {
1438
- return pathDepth(left) - pathDepth(right);
1439
- }
1440
-
1441
- function comparePathDepthDesc(left, right) {
1442
- return pathDepth(right) - pathDepth(left);
1443
- }
1444
-
1445
- function pathDepth(path) {
1446
- return String(path || "").split(".").length;
1447
- }
1448
-
1449
- function compareEntryCreationOrder(left, right) {
1450
- const depthDelta = pathDepth(left.path) - pathDepth(right.path);
1451
- if (depthDelta !== 0) {
1452
- return depthDelta;
1453
- }
1454
-
1455
- return kindRank(left.kind) - kindRank(right.kind);
1456
- }
1457
-
1458
- function kindRank(kind) {
1459
- if (kind === "objectTypeDefinition") {
1460
- return -1;
1461
- }
1462
- if (kind === "folder") {
1463
- return 0;
1464
- }
1465
- if (kind === "object") {
1466
- return 1;
1467
- }
1468
- if (kind === "objectTypeInstance") {
1469
- return 1;
1470
- }
1471
- if (kind === "variable") {
1472
- return 2;
1473
- }
1474
- if (kind === "method") {
1475
- return 3;
1476
- }
1477
- if (kind === "alarm") {
1478
- return 4;
1479
- }
1480
- return 10;
1481
- }
1482
-
1483
- module.exports = {
1484
- OpcUaAddressSpaceBuilder
1485
- };
1
+ "use strict";
2
+
3
+ const {
4
+ DATA_TYPE_MAP,
5
+ StatusCodes,
6
+ Variant,
7
+ VariantArrayType,
8
+ sanitizeNodeIdPath,
9
+ DataType,
10
+ coerceNodeId,
11
+ PermissionType,
12
+ resolveNodeId
13
+ } = require("./opcua-constants");
14
+
15
+ const { OpcUaAddressSpaceAlarm } = require("./opcua-address-space-alarm")
16
+
17
+ class OpcUaAddressSpaceBuilder {
18
+ constructor(options) {
19
+ this.namespace = options.namespace;
20
+ this.namespaces = options.namespaces || new Map([[2, this.namespace]]);
21
+ this.server = options.server;
22
+ this.registry = options.registry;
23
+ this.node = options.node;
24
+ this.serverName = options.serverName;
25
+ const hasUsers = Array.isArray(options.users) && options.users.length > 0;
26
+ this.authorizationDisabled = !hasUsers;
27
+ this.nodeEntries = new Map();
28
+ this.variableStore = new Map();
29
+ this.variableNodeIdStore = new Map();
30
+ this.objectTypeStore = new Map();
31
+ this.alarmStore = new Map();
32
+ this.pendingAlarms = [];
33
+
34
+ this.addressSpace = options.addressSpace;
35
+
36
+
37
+
38
+ this.addressSpaceAlarm = new OpcUaAddressSpaceAlarm({
39
+ namespace: this.namespace,
40
+ registry: this.registry,
41
+ server: this.server,
42
+ node: this.node
43
+ });
44
+ }
45
+
46
+ rebuild(treeConfig) {
47
+
48
+ this.sync(treeConfig, { fullReset: true });
49
+ }
50
+
51
+ sync(treeConfig, options) {
52
+ if (!this.namespace || !this.server) {
53
+ throw new Error("Address space is not initialized");
54
+ }
55
+
56
+ const settings = options || {};
57
+ const desiredEntries = this.collectDesiredEntries(treeConfig);
58
+
59
+
60
+
61
+ if (settings.fullReset) {
62
+ this.clearDynamicNodes();
63
+ }
64
+
65
+ const removalRoots = this.collectRemovalRoots(desiredEntries);
66
+ removalRoots.forEach((path) => this.removeSubtree(path));
67
+
68
+
69
+
70
+ this.updateExistingNodes(desiredEntries);
71
+ this.createMissingNodes(desiredEntries);
72
+ this.flushPendingAlarms();
73
+
74
+ }
75
+
76
+
77
+
78
+ clearDynamicNodes() {
79
+ const paths = Array.from(this.nodeEntries.keys()).sort(comparePathDepthDesc);
80
+
81
+ paths.forEach((path) => this.removeSingleNode(path));
82
+ this.nodeEntries.clear();
83
+ this.variableStore.clear();
84
+ this.variableNodeIdStore.clear();
85
+ this.objectTypeStore.clear();
86
+ if (this.enumerationStore) this.enumerationStore.clear();
87
+ this.alarmStore.clear(); // Adicione esta linha
88
+ this.pendingAlarms = [];
89
+ }
90
+
91
+ readValueByPath(path) {
92
+ return this.getVariableRecordByPath(path).getValue();
93
+ }
94
+
95
+ readValueByNodeId(nodeId) {
96
+ return this.getVariableRecordByNodeId(nodeId).getValue();
97
+ }
98
+
99
+ eventValueByPath(valuePayload) {
100
+
101
+ const path = valuePayload.nodePath
102
+ const value = valuePayload.value
103
+ const message = valuePayload.message
104
+ const severity = valuePayload.severity
105
+ if (!this.nodeEntries.has(path)) {
106
+ throw new Error("Unknown path: " + path);
107
+ }
108
+
109
+ const entry = this.nodeEntries.get(path);
110
+
111
+
112
+
113
+ entry.node.raiseEvent("BaseEventType", {
114
+ sourceName: {
115
+ dataType: DataType.String,
116
+ value: path
117
+ },
118
+ message: {
119
+ dataType: DataType.LocalizedText,
120
+ value: { text: message }
121
+ },
122
+ severity: {
123
+ dataType: DataType.UInt16,
124
+ value: severity
125
+ }
126
+ });
127
+
128
+ }
129
+
130
+ writeValueByPath(path, value) {
131
+ return this.getVariableRecordByPath(path).setValue(value);
132
+ }
133
+
134
+ writeValueByNodeId(nodeId, value) {
135
+ return this.getVariableRecordByNodeId(nodeId).setValue(value);
136
+ }
137
+
138
+ readValue(identifierType, identifier) {
139
+ const record = this.getVariableRecord(identifierType, identifier);
140
+ let val = record.getValue();
141
+ if (record.type === "Int64" || record.type === "UInt64") {
142
+ const convertToNumber = (v) => {
143
+ const num = Number(v);
144
+ return Number.isFinite(num) ? num : v;
145
+ };
146
+ if (record.isArray) {
147
+ val = Array.isArray(val) ? val.map(convertToNumber) : convertToNumber(val);
148
+ } else {
149
+ val = convertToNumber(val);
150
+ }
151
+ }
152
+ return val;
153
+ }
154
+
155
+ writeValue(identifierType, identifier, value) {
156
+ return this.getVariableRecord(identifierType, identifier).setValue(value);
157
+ }
158
+
159
+ collectDesiredEntries(treeConfig) {
160
+ const desiredEntries = new Map();
161
+ const objectTypeConfigs = this.buildObjectTypeConfigMap(treeConfig);
162
+
163
+ (Array.isArray(treeConfig.objectsTypes) ? treeConfig.objectsTypes : []).forEach((objectTypeConfig) => {
164
+ this.collectObjectTypeDefinition(desiredEntries, objectTypeConfig, objectTypeConfigs);
165
+ });
166
+
167
+ (Array.isArray(treeConfig.enumerations) ? treeConfig.enumerations : []).forEach((enumerationConfig) => {
168
+ this.collectEnumerationDefinition(desiredEntries, enumerationConfig);
169
+ });
170
+
171
+ (Array.isArray(treeConfig.folders) ? treeConfig.folders : []).forEach((folderConfig) => {
172
+ this.collectBranch(desiredEntries, "folder", folderConfig, "", "organizedBy", objectTypeConfigs);
173
+ });
174
+
175
+ (Array.isArray(treeConfig.objects) ? treeConfig.objects : []).forEach((objectConfig) => {
176
+ this.collectBranch(desiredEntries, "object", objectConfig, "", "organizedBy", objectTypeConfigs);
177
+ });
178
+
179
+ return desiredEntries;
180
+ }
181
+
182
+ buildObjectTypeConfigMap(treeConfig) {
183
+ const objectTypeConfigs = new Map();
184
+
185
+ (Array.isArray(treeConfig.objectsTypes) ? treeConfig.objectsTypes : []).forEach((objectTypeConfig) => {
186
+ if (objectTypeConfigs.has(objectTypeConfig.name)) {
187
+ throw new Error("Duplicate object type name: " + objectTypeConfig.name);
188
+ }
189
+ objectTypeConfigs.set(objectTypeConfig.name, objectTypeConfig);
190
+ });
191
+
192
+ return objectTypeConfigs;
193
+ }
194
+
195
+ collectObjectTypeDefinition(desiredEntries, config, objectTypeConfigs) {
196
+ const path = this.buildObjectTypePath(config.name);
197
+ desiredEntries.set(path, this.buildEntryDefinition("objectTypeDefinition", config, path, "", "typeDefinition"));
198
+ this.collectBranchChildren(desiredEntries, config, path, "componentOf", objectTypeConfigs, {
199
+ skipAlarms: false,
200
+ preserveCollectionNames: true,
201
+ typeRootPath: path
202
+ });
203
+ }
204
+
205
+ collectEnumerationDefinition(desiredEntries, config) {
206
+ const path = this.buildEnumerationPath(config.name);
207
+ desiredEntries.set(path, this.buildEntryDefinition("enumeration", config, path, "", "typeDefinition"));
208
+ }
209
+
210
+ collectBranch(desiredEntries, kind, config, parentPath, relationship, objectTypeConfigs, options) {
211
+ const settings = options || {};
212
+ const path = settings.preserveCollectionNames
213
+ ? this.buildCollectionPath(parentPath, kind === "folder" ? "folders" : "objects", config.name)
214
+ : this.buildPath(parentPath, config.name);
215
+ desiredEntries.set(path, this.buildEntryDefinition(kind, config, path, parentPath, relationship));
216
+ this.collectBranchChildren(
217
+ desiredEntries,
218
+ config,
219
+ path,
220
+ kind === "folder" ? "organizedBy" : "componentOf",
221
+ objectTypeConfigs,
222
+ settings
223
+ );
224
+ }
225
+
226
+ collectBranchChildren(desiredEntries, config, path, childRelationship, objectTypeConfigs, options) {
227
+ const settings = options || {};
228
+ const preserveCollectionNames = !!settings.preserveCollectionNames;
229
+ (Array.isArray(config.folders) ? config.folders : []).forEach((folderConfig) => {
230
+ this.collectBranch(
231
+ desiredEntries,
232
+ "folder",
233
+ folderConfig,
234
+ path,
235
+ childRelationship,
236
+ objectTypeConfigs,
237
+ settings
238
+ );
239
+ });
240
+
241
+ (Array.isArray(config.objects) ? config.objects : []).forEach((objectConfig) => {
242
+ this.collectBranch(
243
+ desiredEntries,
244
+ "object",
245
+ objectConfig,
246
+ path,
247
+ childRelationship,
248
+ objectTypeConfigs,
249
+ settings
250
+ );
251
+ });
252
+
253
+ (Array.isArray(config.variables) ? config.variables : []).forEach((variableConfig) => {
254
+ const childPath = preserveCollectionNames
255
+ ? this.buildCollectionPath(path, "variables", variableConfig.name)
256
+ : this.buildPath(path, variableConfig.name);
257
+ desiredEntries.set(
258
+ childPath,
259
+ this.buildEntryDefinition("variable", variableConfig, childPath, path, "componentOf")
260
+ );
261
+ });
262
+
263
+ (Array.isArray(config.methods) ? config.methods : []).forEach((methodConfig) => {
264
+ const childPath = preserveCollectionNames
265
+ ? this.buildCollectionPath(path, "methods", methodConfig.name)
266
+ : this.buildPath(path, methodConfig.name);
267
+ desiredEntries.set(
268
+ childPath,
269
+ this.buildEntryDefinition("method", methodConfig, childPath, path, "componentOf")
270
+ );
271
+ });
272
+
273
+ (Array.isArray(config.objectsTypes) ? config.objectsTypes : []).forEach((objectTypeInstanceConfig) => {
274
+ this.collectObjectTypeInstance(
275
+ desiredEntries,
276
+ objectTypeInstanceConfig,
277
+ path,
278
+ childRelationship,
279
+ objectTypeConfigs,
280
+ settings
281
+ );
282
+ });
283
+
284
+ if (!settings.skipAlarms) {
285
+ //alarmes
286
+ (Array.isArray(config.alarms) ? config.alarms : []).forEach((alarmConfig) => {
287
+ const childPath = preserveCollectionNames
288
+ ? this.buildCollectionPath(path, "alarms", alarmConfig.name)
289
+ : this.buildPath(path, alarmConfig.name);
290
+
291
+ desiredEntries.set(
292
+ childPath,
293
+ this.buildEntryDefinition("alarm", alarmConfig, childPath, path, "componentOf")
294
+ );
295
+ //this.addAlarmPlaceholder(path, alarmConfig);
296
+ });
297
+ }
298
+ }
299
+
300
+ collectObjectTypeInstance(desiredEntries, instanceConfig, parentPath, relationship, objectTypeConfigs, options) {
301
+ const path = this.buildPath(parentPath, instanceConfig.name);
302
+ desiredEntries.set(
303
+ path,
304
+ this.buildEntryDefinition("objectTypeInstance", instanceConfig, path, parentPath, relationship)
305
+ );
306
+ // Variables, methods and nested objectsTypes inherited from the type definition
307
+ // are instantiated automatically by node-opcua when addObject({ typeDefinition })
308
+ // is called. We must NOT add them as independent desiredEntries — that would
309
+ // create duplicate nodes on top of the ones the lib already created.
310
+ // Only extra children defined directly on the instance (not inherited) are added here.
311
+ const instanceOnlyConfig = {
312
+ folders: Array.isArray(instanceConfig.folders) ? instanceConfig.folders : [],
313
+ objects: Array.isArray(instanceConfig.objects) ? instanceConfig.objects : [],
314
+ variables: Array.isArray(instanceConfig.variables) ? instanceConfig.variables : [],
315
+ methods: Array.isArray(instanceConfig.methods) ? instanceConfig.methods : [],
316
+ alarms: Array.isArray(instanceConfig.alarms) ? instanceConfig.alarms : [],
317
+ objectsTypes: Array.isArray(instanceConfig.objectsTypes) ? instanceConfig.objectsTypes : []
318
+ };
319
+ this.collectBranchChildren(desiredEntries, instanceOnlyConfig, path, "componentOf", objectTypeConfigs, Object.assign({}, options, {
320
+ skipAlarms: false,
321
+ preserveCollectionNames: false
322
+ }));
323
+ }
324
+
325
+ expandObjectTypeInstanceConfig(instanceConfig, objectTypeConfigs, stack) {
326
+ const typeName = instanceConfig.objectsType;
327
+ if (stack.indexOf(typeName) !== -1) {
328
+ throw new Error("Circular object type reference: " + stack.concat(typeName).join(" -> "));
329
+ }
330
+
331
+ const baseConfig = objectTypeConfigs.get(typeName);
332
+ if (!baseConfig) {
333
+ throw new Error("Unknown object type: " + typeName);
334
+ }
335
+
336
+ return this.mergeObjectTypeConfigs(baseConfig, instanceConfig, objectTypeConfigs, stack.concat(typeName));
337
+ }
338
+
339
+ mergeObjectTypeConfigs(baseConfig, instanceConfig, objectTypeConfigs, stack) {
340
+ const merged = {
341
+ name: instanceConfig.name,
342
+ displayName: instanceConfig.displayName || instanceConfig.name,
343
+ description: instanceConfig.description || "",
344
+ folders: this.cloneConfigs(baseConfig.folders),
345
+ objects: this.cloneConfigs(baseConfig.objects),
346
+ variables: this.cloneConfigs(baseConfig.variables),
347
+ methods: this.cloneConfigs(baseConfig.methods),
348
+ alarms: this.cloneConfigs(baseConfig.alarms),
349
+ objectsTypes: []
350
+ };
351
+
352
+ const baseInstances = Array.isArray(baseConfig.objectsTypes) ? baseConfig.objectsTypes : [];
353
+ const instanceOverrides = Array.isArray(instanceConfig.objectsTypes) ? instanceConfig.objectsTypes : [];
354
+
355
+ merged.objectsTypes = baseInstances
356
+ .map((nestedInstanceConfig) => this.mergeNestedObjectTypeInstance(nestedInstanceConfig, objectTypeConfigs, stack))
357
+ .concat(this.cloneConfigs(instanceOverrides));
358
+
359
+ merged.folders = merged.folders.concat(this.cloneConfigs(instanceConfig.folders));
360
+ merged.objects = merged.objects.concat(this.cloneConfigs(instanceConfig.objects));
361
+ merged.variables = merged.variables.concat(this.cloneConfigs(instanceConfig.variables));
362
+ merged.methods = merged.methods.concat(this.cloneConfigs(instanceConfig.methods));
363
+ merged.alarms = merged.alarms.concat(this.cloneConfigs(instanceConfig.alarms));
364
+
365
+ return merged;
366
+ }
367
+
368
+ mergeNestedObjectTypeInstance(instanceConfig, objectTypeConfigs, stack) {
369
+ const cloned = this.cloneConfig(instanceConfig);
370
+ if (cloned && cloned.objectsType) {
371
+ const typeName = cloned.objectsType;
372
+ if (stack.indexOf(typeName) !== -1) {
373
+ throw new Error("Circular object type reference: " + stack.concat(typeName).join(" -> "));
374
+ }
375
+ if (!objectTypeConfigs.has(typeName)) {
376
+ throw new Error("Unknown object type: " + typeName);
377
+ }
378
+ }
379
+ return cloned;
380
+ }
381
+
382
+ cloneConfigs(items) {
383
+ return Array.isArray(items) ? items.map((item) => this.cloneConfig(item)) : [];
384
+ }
385
+
386
+ cloneConfig(item) {
387
+ return item ? JSON.parse(JSON.stringify(item)) : item;
388
+ }
389
+
390
+ rewriteAlarmVariablePaths(branchConfig, currentPath, rootInstancePath) {
391
+ if (!branchConfig || typeof branchConfig !== "object") {
392
+ return;
393
+ }
394
+
395
+ (Array.isArray(branchConfig.alarms) ? branchConfig.alarms : []).forEach((alarmConfig) => {
396
+ const variableNodeId = String(alarmConfig.variableNodeId || "").trim();
397
+ if (!variableNodeId) {
398
+ return;
399
+ }
400
+
401
+ if (variableNodeId.indexOf(".") === 0) {
402
+ alarmConfig.variableNodeId = this.resolveObjectTypeRelativeReference(rootInstancePath, variableNodeId);
403
+ return;
404
+ }
405
+
406
+ if (variableNodeId.indexOf(currentPath + ".") === 0 || variableNodeId === currentPath) {
407
+ return;
408
+ }
409
+
410
+ if (variableNodeId.indexOf(rootInstancePath + ".") === 0 || variableNodeId === rootInstancePath) {
411
+ return;
412
+ }
413
+
414
+ if (variableNodeId.indexOf("__objectTypes.") === 0) {
415
+ return;
416
+ }
417
+
418
+ alarmConfig.variableNodeId = currentPath + "." + variableNodeId;
419
+ });
420
+
421
+ (Array.isArray(branchConfig.folders) ? branchConfig.folders : []).forEach((folderConfig) => {
422
+ this.rewriteAlarmVariablePaths(folderConfig, this.buildPath(currentPath, folderConfig.name), rootInstancePath);
423
+ });
424
+
425
+ (Array.isArray(branchConfig.objects) ? branchConfig.objects : []).forEach((objectConfig) => {
426
+ this.rewriteAlarmVariablePaths(objectConfig, this.buildPath(currentPath, objectConfig.name), rootInstancePath);
427
+ });
428
+ }
429
+
430
+ buildEntryDefinition(kind, config, path, parentPath, relationship) {
431
+ return {
432
+ kind,
433
+ path,
434
+ parentPath,
435
+ relationship,
436
+ config,
437
+ signature: this.buildSignature(kind, config, parentPath, relationship)
438
+ };
439
+ }
440
+
441
+ normalizeAccessPermissions(config) {
442
+ const rawPermissions = config && (config.accessPermission || config.accessPermissions);
443
+ if (!Array.isArray(rawPermissions) || !rawPermissions.length) {
444
+ return ["public"];
445
+ }
446
+
447
+ const seen = new Set();
448
+ const normalized = rawPermissions.reduce((result, value) => {
449
+ const permission = String(value || "").trim().toLowerCase();
450
+ if (!permission || seen.has(permission)) {
451
+ return result;
452
+ }
453
+ seen.add(permission);
454
+ result.push(permission);
455
+ return result;
456
+ }, []);
457
+
458
+ return normalized.length ? normalized : ["public"];
459
+ }
460
+
461
+ resolvePermissionRoleId(permission) {
462
+ const normalized = String(permission || "").trim().toLowerCase();
463
+ if (!normalized) {
464
+ return null;
465
+ }
466
+
467
+ const wellKnownRoles = {
468
+ public: ["WellKnownRole_Anonymous", "WellKnownRole_AuthenticatedUser"],
469
+ anonymous: ["WellKnownRole_Anonymous"],
470
+ authenticated: ["WellKnownRole_AuthenticatedUser"],
471
+ authenticateduser: ["WellKnownRole_AuthenticatedUser"],
472
+ operator: ["WellKnownRole_Operator"],
473
+ supervisor: ["WellKnownRole_Supervisor"],
474
+ engineer: ["WellKnownRole_Engineer"],
475
+ engineering: ["WellKnownRole_Engineer"],
476
+ observer: ["WellKnownRole_Observer"],
477
+ admin: ["WellKnownRole_ConfigureAdmin"],
478
+ configureadmin: ["WellKnownRole_ConfigureAdmin"],
479
+ securityadmin: ["WellKnownRole_SecurityAdmin"]
480
+ };
481
+
482
+ if (wellKnownRoles[normalized]) {
483
+ return wellKnownRoles[normalized].map((roleId) => resolveNodeId(roleId));
484
+ }
485
+
486
+ return [resolveNodeId("ns=1;s=NodeRedRole/" + this.sanitizeRoleSegment(normalized))];
487
+ }
488
+
489
+ sanitizeRoleSegment(value) {
490
+ return String(value || "")
491
+ .trim()
492
+ .toLowerCase()
493
+ .replace(/\s+/g, "_")
494
+ .replace(/[^a-z0-9._-]/g, "_");
495
+ }
496
+
497
+ buildPermissionMask(kind, config) {
498
+ if (kind === "method") {
499
+ return PermissionType.Browse | PermissionType.Call;
500
+ }
501
+
502
+ const basePermissions = PermissionType.Browse | PermissionType.Read;
503
+ if (kind === "alarm") {
504
+ return basePermissions | PermissionType.ReceiveEvents;
505
+ }
506
+
507
+ if (kind === "variable" && config && config.access === "readwrite") {
508
+ return basePermissions | PermissionType.Write;
509
+ }
510
+
511
+ return basePermissions;
512
+ }
513
+
514
+ buildRolePermissions(kind, config) {
515
+ if (this.authorizationDisabled) {
516
+ return undefined;
517
+ }
518
+
519
+ const permissions = this.normalizeAccessPermissions(config);
520
+ const permissionMask = this.buildPermissionMask(kind, config);
521
+ const rolePermissions = [];
522
+ const seen = new Set();
523
+
524
+ permissions.forEach((permission) => {
525
+ const roleIds = this.resolvePermissionRoleId(permission);
526
+ (roleIds || []).forEach((roleId) => {
527
+ const key = coerceNodeId(roleId).toString();
528
+ if (seen.has(key)) {
529
+ return;
530
+ }
531
+ seen.add(key);
532
+ rolePermissions.push({
533
+ roleId,
534
+ permissions: permissionMask
535
+ });
536
+ });
537
+ });
538
+
539
+ return rolePermissions.length ? rolePermissions : undefined;
540
+ }
541
+
542
+ buildSignature(kind, config, parentPath, relationship) {
543
+ if (kind === "variable") {
544
+ return JSON.stringify({
545
+ kind,
546
+ parentPath,
547
+ relationship,
548
+ nodeId: config.nodeId || "",
549
+ namespaceId: this.resolveNamespaceId(config),
550
+ displayName: config.displayName || config.name,
551
+ description: config.description || "",
552
+ type: config.type,
553
+ access: config.access,
554
+ isArray: this.isArrayValue(config.value),
555
+ accessPermission: this.normalizeAccessPermissions(config)
556
+ });
557
+ }
558
+
559
+ if (kind === "method") {
560
+ return JSON.stringify({
561
+ kind,
562
+ parentPath,
563
+ relationship,
564
+ nodeId: config.nodeId || "",
565
+ namespaceId: this.resolveNamespaceId(config),
566
+ displayName: config.displayName || config.name,
567
+ description: config.description || "",
568
+ accessPermission: this.normalizeAccessPermissions(config),
569
+ inputs: Array.isArray(config.inputs) ? config.inputs : [],
570
+ outputs: Array.isArray(config.outputs) ? config.outputs : []
571
+ });
572
+ }
573
+
574
+ if (kind === "objectTypeInstance") {
575
+ return JSON.stringify({
576
+ kind,
577
+ parentPath,
578
+ relationship,
579
+ nodeId: config.nodeId || "",
580
+ namespaceId: this.resolveNamespaceId(config),
581
+ displayName: config.displayName || config.name,
582
+ description: config.description || "",
583
+ accessPermission: this.normalizeAccessPermissions(config),
584
+ objectsType: config.objectsType
585
+ });
586
+ }
587
+
588
+ return JSON.stringify({
589
+ kind,
590
+ parentPath,
591
+ relationship,
592
+ nodeId: config.nodeId || "",
593
+ namespaceId: this.resolveNamespaceId(config),
594
+ displayName: config.displayName || config.name,
595
+ description: config.description || "",
596
+ accessPermission: this.normalizeAccessPermissions(config)
597
+ });
598
+ }
599
+
600
+ collectRemovalRoots(desiredEntries) {
601
+ const candidates = [];
602
+
603
+ this.nodeEntries.forEach((entry, path) => {
604
+ const desired = desiredEntries.get(path);
605
+
606
+ if (!desired) {
607
+ candidates.push(path);
608
+ return;
609
+ }
610
+
611
+ if (entry.kind !== desired.kind || entry.signature !== desired.signature) {
612
+ candidates.push(path);
613
+ }
614
+ });
615
+
616
+ return collapsePaths(candidates);
617
+ }
618
+
619
+ updateExistingNodes(desiredEntries) {
620
+ desiredEntries.forEach((desired, path) => {
621
+ const existing = this.nodeEntries.get(path);
622
+ if (!existing || existing.kind !== desired.kind) {
623
+ return;
624
+ }
625
+
626
+ if (existing.kind === "variable") {
627
+ const record = this.variableStore.get(path);
628
+ if (record) {
629
+ record.setRuntimeValue(desired.config.value);
630
+ }
631
+ }
632
+ });
633
+ }
634
+
635
+ createMissingNodes(desiredEntries) {
636
+ const ordered = Array.from(desiredEntries.values()).sort(compareEntryCreationOrder);
637
+ ordered.forEach((definition) => {
638
+ if (this.nodeEntries.has(definition.path)) {
639
+ return;
640
+ }
641
+
642
+ this.createNode(definition);
643
+ });
644
+ }
645
+
646
+ createNode(definition) {
647
+ const parentNode = this.resolveParentNode(definition.parentPath);
648
+
649
+
650
+
651
+ if (definition.kind === "folder") {
652
+ this.addFolder(parentNode, definition.config, definition.parentPath, definition.relationship, definition.path);
653
+ return;
654
+ }
655
+
656
+ if (definition.kind === "objectTypeDefinition") {
657
+
658
+ this.addObjectTypeDefinition(definition.config);
659
+ return;
660
+ }
661
+
662
+ if (definition.kind === "enumeration") {
663
+ this.addEnumerationTypeDefinition(definition.config);
664
+ return;
665
+ }
666
+
667
+ if (definition.kind === "object") {
668
+ this.addObject(parentNode, definition.config, definition.parentPath, definition.relationship, definition.path);
669
+ return;
670
+ }
671
+
672
+ if (definition.kind === "objectTypeInstance") {
673
+ this.addObjectTypeInstance(parentNode, definition.config, definition.parentPath, definition.relationship, definition.path);
674
+ return;
675
+ }
676
+
677
+ if (definition.kind === "alarm") {
678
+ this.addAlarm(parentNode, definition.config, definition.parentPath, definition.relationship, definition.path);
679
+ return;
680
+
681
+ }
682
+
683
+ if (definition.kind === "variable") {
684
+ this.addVariable(parentNode, definition.config, definition.parentPath, definition.path);
685
+ return;
686
+ }
687
+
688
+ if (definition.kind === "method") {
689
+ this.addMethod(parentNode, definition.config, definition.parentPath, definition.path);
690
+ }
691
+
692
+
693
+ }
694
+
695
+ resolveParentNode(parentPath) {
696
+ if (!parentPath) {
697
+ return this.server.engine.addressSpace.rootFolder.objects;
698
+ }
699
+
700
+ const parentEntry = this.nodeEntries.get(parentPath);
701
+ if (!parentEntry || !parentEntry.node) {
702
+ throw new Error("Parent node is not available for path: " + parentPath);
703
+ }
704
+
705
+ return parentEntry.node;
706
+ }
707
+
708
+ removeSubtree(rootPath) {
709
+ const affectedPaths = Array.from(this.nodeEntries.keys())
710
+ .filter((path) => isSamePathOrDescendant(path, rootPath))
711
+ .sort(comparePathDepthDesc);
712
+
713
+ affectedPaths.forEach((path) => this.removeSingleNode(path));
714
+ }
715
+
716
+ removeSingleNode(path) {
717
+ const entry = this.nodeEntries.get(path);
718
+ if (!entry) {
719
+ return;
720
+ }
721
+
722
+ if (entry.kind === "variable") {
723
+ const record = this.variableStore.get(path);
724
+ if (record && record.nodeIdKey) {
725
+ this.variableNodeIdStore.delete(record.nodeIdKey);
726
+ }
727
+ this.variableStore.delete(path);
728
+ }
729
+
730
+ if (entry.kind === "objectTypeDefinition") {
731
+ this.objectTypeStore.delete(entry.config.name);
732
+ }
733
+
734
+ if (entry.kind === "enumeration" && this.enumerationStore) {
735
+ this.enumerationStore.delete(entry.config.name);
736
+ }
737
+
738
+ try {
739
+ entry.namespace.deleteNode(entry.node);
740
+
741
+ } catch (error) {
742
+ console.error("error removeSingleNode")
743
+ console.error(error)
744
+ this.node.debug("Ignoring deleteNode error for " + path + ": " + error.message);
745
+ }
746
+
747
+
748
+
749
+ this.nodeEntries.delete(path);
750
+ }
751
+
752
+ addObject(parentNode, objectConfig, parentPath, relationship, pathOverride) {
753
+ const namespace = this.getNamespaceForConfig(objectConfig);
754
+
755
+ const addressSpace = this.server.engine.addressSpace;
756
+ const serverNode = addressSpace.rootFolder.objects.server;
757
+
758
+
759
+ const objectName = objectConfig.name;
760
+ const nextPath = pathOverride || this.buildPath(parentPath, objectName);
761
+ const options = {
762
+ browseName: objectConfig.displayName || objectName,
763
+ displayName: objectConfig.displayName || objectName,
764
+ description: objectConfig.description || "",
765
+ nodeId: this.resolveNodeId(objectConfig, nextPath, namespace),
766
+ rolePermissions: this.buildRolePermissions("object", objectConfig),
767
+ eventNotifier: 1, //enabled_events,
768
+ eventSourceOf: serverNode
769
+ };
770
+
771
+ if (relationship === "organizedBy") {
772
+ options.organizedBy = parentNode;
773
+ } else {
774
+ options.componentOf = parentNode;
775
+ }
776
+
777
+ if (this.isObjectTypePath(parentPath)) {
778
+ options.modellingRule = "Mandatory";
779
+ }
780
+
781
+ const objectNode = namespace.addObject(options);
782
+ this.registerNodeEntry("object", nextPath, parentPath, relationship, objectConfig, objectNode, namespace);
783
+ }
784
+
785
+ addObjectTypeDefinition(objectTypeConfig) {
786
+ const namespace = this.getNamespaceForConfig(objectTypeConfig);
787
+ const objectTypeNode = namespace.addObjectType({
788
+ browseName: objectTypeConfig.name,
789
+ displayName: objectTypeConfig.displayName || objectTypeConfig.name,
790
+ description: objectTypeConfig.description || "",
791
+ nodeId: this.resolveNodeId(objectTypeConfig, this.buildObjectTypePath(objectTypeConfig.name), namespace),
792
+ rolePermissions: this.buildRolePermissions("objectTypeDefinition", objectTypeConfig),
793
+ subtypeOf: "BaseObjectType"
794
+ });
795
+
796
+
797
+ const path = this.buildObjectTypePath(objectTypeConfig.name);
798
+
799
+
800
+
801
+ this.registerNodeEntry("objectTypeDefinition", path, "", "typeDefinition", objectTypeConfig, objectTypeNode, namespace);
802
+ this.objectTypeStore.set(objectTypeConfig.name, {
803
+ node: objectTypeNode,
804
+ config: objectTypeConfig,
805
+ path: path,
806
+ namespace: namespace
807
+ });
808
+ }
809
+
810
+ addEnumerationTypeDefinition(config) {
811
+ const namespace = this.getNamespaceForConfig(config);
812
+ const enumTypeNode = namespace.addEnumerationType({
813
+ browseName: config.displayName || config.name,
814
+ displayName: config.displayName || config.name,
815
+ description: config.description || "",
816
+ nodeId: this.resolveNodeId(config, this.buildEnumerationPath(config.name), namespace),
817
+ enumeration: config.enumeration
818
+ });
819
+
820
+ const path = this.buildEnumerationPath(config.name);
821
+
822
+ this.registerNodeEntry("enumeration", path, "", "typeDefinition", config, enumTypeNode, namespace);
823
+ if (!this.enumerationStore) this.enumerationStore = new Map();
824
+ this.enumerationStore.set(config.name, {
825
+ node: enumTypeNode,
826
+ config: config,
827
+ path: path,
828
+ namespace: namespace
829
+ });
830
+ }
831
+
832
+ addObjectTypeInstance(parentNode, instanceConfig, parentPath, relationship, pathOverride) {
833
+ const objectTypeEntry = this.objectTypeStore.get(instanceConfig.objectsType);
834
+ if (!objectTypeEntry || !objectTypeEntry.node) {
835
+ throw new Error("Object type is not available for instance " + instanceConfig.name + ": " + instanceConfig.objectsType);
836
+ }
837
+
838
+ const objectName = instanceConfig.name;
839
+ const nextPath = pathOverride || this.buildPath(parentPath, objectName);
840
+ const namespace = this.getNamespaceForConfig(instanceConfig);
841
+
842
+ const options = {
843
+ browseName: instanceConfig.displayName || objectName,
844
+ displayName: instanceConfig.displayName || objectName,
845
+ description: instanceConfig.description || "",
846
+ nodeId: this.resolveNodeId(instanceConfig, nextPath, namespace),
847
+ rolePermissions: this.buildRolePermissions("objectTypeInstance", instanceConfig),
848
+ typeDefinition: objectTypeEntry.node.nodeId,
849
+ eventNotifier: 1
850
+ };
851
+
852
+ if (relationship === "organizedBy") {
853
+ options.organizedBy = parentNode;
854
+ } else {
855
+ options.componentOf = parentNode;
856
+ }
857
+
858
+ const objectNode = namespace.addObject(options);
859
+ this.registerNodeEntry("objectTypeInstance", nextPath, parentPath, relationship, instanceConfig, objectNode, namespace);
860
+
861
+ // Create the inherited children explicitly using the configs that opcua-config.js
862
+ // already rewrote with the correct instance nodeIds (e.g. server1.newObjectType.cmd_on).
863
+ // We do NOT call instantiate() because node-opcua cannot auto-assign nodeIds for the
864
+ // cloned children when the type children already have explicit string nodeIds registered.
865
+ this.createInheritedChildren(objectNode, nextPath, instanceConfig, namespace);
866
+ }
867
+
868
+ createInheritedChildren(instanceNode, instancePath, instanceConfig, namespace) {
869
+ const typeEntry = this.objectTypeStore.get(instanceConfig.objectsType);
870
+ if (!typeEntry) return;
871
+
872
+ // Extract the type's nodeId value prefix (e.g. "Motor_type2") and the
873
+ // instance's nodeId value prefix (e.g. "server1.newObjectType") so we can
874
+ // rewrite every child nodeId from the type to the instance on-the-fly.
875
+ const typeNodeId = typeEntry.config.nodeId || "";
876
+ const instanceNodeId = instanceConfig.nodeId || "";
877
+ const typePrefix = this.extractNodeIdStringValue(typeNodeId);
878
+ const instancePrefix = this.extractNodeIdStringValue(instanceNodeId);
879
+
880
+ this.createInheritedBranchChildren(typeEntry.config, instanceNode, instancePath, typePrefix, instancePrefix);
881
+ }
882
+
883
+ extractNodeIdStringValue(nodeId) {
884
+ const m = String(nodeId || "").match(/(?:^|;)s=(.+)$/);
885
+ return m ? m[1] : "";
886
+ }
887
+
888
+ rewriteInheritedNodeId(nodeId, typePrefix, instancePrefix) {
889
+ if (!nodeId || !typePrefix || !instancePrefix) return nodeId;
890
+ const m = String(nodeId).match(/^(ns=\d+;s=)([\s\S]*)$/);
891
+ if (m && m[2].startsWith(typePrefix)) {
892
+ return m[1] + instancePrefix + m[2].slice(typePrefix.length);
893
+ }
894
+ return nodeId;
895
+ }
896
+
897
+ createInheritedBranchChildren(typeConfig, parentOpcNode, parentPath, typePrefix, instancePrefix) {
898
+ const variables = Array.isArray(typeConfig.variables) ? typeConfig.variables : [];
899
+ const methods = Array.isArray(typeConfig.methods) ? typeConfig.methods : [];
900
+ const folders = Array.isArray(typeConfig.folders) ? typeConfig.folders : [];
901
+ const objects = Array.isArray(typeConfig.objects) ? typeConfig.objects : [];
902
+
903
+ variables.forEach((varConfig) => {
904
+ const childPath = this.buildPath(parentPath, varConfig.name);
905
+ const rewrittenConfig = Object.assign({}, varConfig, {
906
+ nodeId: this.rewriteInheritedNodeId(varConfig.nodeId, typePrefix, instancePrefix)
907
+ });
908
+ this.addVariable(parentOpcNode, rewrittenConfig, parentPath, childPath);
909
+ });
910
+
911
+ methods.forEach((methodConfig) => {
912
+ const childPath = this.buildPath(parentPath, methodConfig.name);
913
+ const rewrittenConfig = Object.assign({}, methodConfig, {
914
+ nodeId: this.rewriteInheritedNodeId(methodConfig.nodeId, typePrefix, instancePrefix)
915
+ });
916
+ this.addMethod(parentOpcNode, rewrittenConfig, parentPath, childPath);
917
+ });
918
+
919
+ folders.forEach((folderConfig) => {
920
+ const childPath = this.buildPath(parentPath, folderConfig.name);
921
+ const rewrittenConfig = Object.assign({}, folderConfig, {
922
+ nodeId: this.rewriteInheritedNodeId(folderConfig.nodeId, typePrefix, instancePrefix)
923
+ });
924
+ this.addFolder(parentOpcNode, rewrittenConfig, parentPath, "componentOf", childPath);
925
+ const childNode = this.nodeEntries.get(childPath) && this.nodeEntries.get(childPath).node;
926
+ if (childNode) {
927
+ this.createInheritedBranchChildren(folderConfig, childNode, childPath, typePrefix, instancePrefix);
928
+ }
929
+ });
930
+
931
+ objects.forEach((objectConfig) => {
932
+ const childPath = this.buildPath(parentPath, objectConfig.name);
933
+ const rewrittenConfig = Object.assign({}, objectConfig, {
934
+ nodeId: this.rewriteInheritedNodeId(objectConfig.nodeId, typePrefix, instancePrefix)
935
+ });
936
+ this.addObject(parentOpcNode, rewrittenConfig, parentPath, "componentOf", childPath);
937
+ const childNode = this.nodeEntries.get(childPath) && this.nodeEntries.get(childPath).node;
938
+ if (childNode) {
939
+ this.createInheritedBranchChildren(objectConfig, childNode, childPath, typePrefix, instancePrefix);
940
+ }
941
+ });
942
+ }
943
+
944
+ addAlarm(parentNode, alarmConfig, parentPath, relationship, pathOverride) {
945
+
946
+
947
+
948
+ try {
949
+
950
+ const name = alarmConfig.name;
951
+ const variableNodeId = alarmConfig.variableNodeId;
952
+ const objectName = alarmConfig.name;
953
+ const nextPath = pathOverride || this.buildPath(parentPath, objectName);
954
+ const path = pathOverride || this.buildPath(parentPath, name);
955
+ const namespace = this.getNamespaceForConfig(alarmConfig);
956
+ const options = {
957
+ browseName: alarmConfig.displayName || objectName,
958
+ displayName: alarmConfig.displayName || objectName,
959
+ description: alarmConfig.description || "",
960
+ nodeId: this.resolveNodeId(alarmConfig, nextPath, namespace),
961
+ eventNotifier: 1 //enabled_events
962
+ };
963
+
964
+ if (relationship === "organizedBy") {
965
+ options.organizedBy = parentNode;
966
+ } else {
967
+ options.componentOf = parentNode;
968
+ }
969
+
970
+ const variableToMonitor = this.resolveAlarmVariableRecord(parentPath, parentNode, variableNodeId)
971
+
972
+ const sourceName = variableToMonitor.node.displayName[0].text
973
+
974
+ const browseName = alarmConfig.displayName || objectName
975
+ const inputNode = variableToMonitor.node
976
+
977
+
978
+
979
+ const conditionName = alarmConfig.displayName || objectName
980
+ const nodeId = this.resolveNodeId(alarmConfig, nextPath, namespace)
981
+
982
+ const alarmNode = this.addressSpaceAlarm.createAlarm(namespace, browseName, parentNode, inputNode, conditionName, nodeId, sourceName, alarmConfig)
983
+ const alarmRolePermissions = this.buildRolePermissions("alarm", alarmConfig);
984
+ if (alarmNode && alarmRolePermissions) {
985
+ alarmNode.setRolePermissions(alarmRolePermissions);
986
+ }
987
+
988
+
989
+
990
+
991
+ // register alarm
992
+ variableToMonitor.alarm = {
993
+ node: alarmNode,
994
+ alarmConfig: alarmConfig,
995
+ }
996
+ // const alarmNode = this.namespace.instantiateOffNormalAlarm(options);
997
+ this.registerNodeEntry("alarm", nextPath, parentPath, relationship, alarmConfig, alarmNode, namespace);
998
+
999
+
1000
+
1001
+
1002
+ } catch (error) {
1003
+
1004
+ if (error && error.message && error.message.indexOf("Unknown alarm variable reference:") === 0) {
1005
+ this.queuePendingAlarm(parentNode, alarmConfig, parentPath, relationship);
1006
+ return;
1007
+ }
1008
+ console.error("erro addAlarm")
1009
+ console.error(error)
1010
+ }
1011
+
1012
+
1013
+ }
1014
+
1015
+ queuePendingAlarm(parentNode, alarmConfig, parentPath, relationship) {
1016
+ const path = this.buildPath(parentPath, alarmConfig.name);
1017
+ const alreadyQueued = this.pendingAlarms.some((item) => item.path === path);
1018
+ if (alreadyQueued) {
1019
+ return;
1020
+ }
1021
+
1022
+ this.pendingAlarms.push({
1023
+ path: path,
1024
+ parentNode: parentNode,
1025
+ alarmConfig: this.cloneConfig(alarmConfig),
1026
+ parentPath: parentPath,
1027
+ relationship: relationship
1028
+ });
1029
+ }
1030
+
1031
+ flushPendingAlarms() {
1032
+ if (!this.pendingAlarms.length) {
1033
+ return;
1034
+ }
1035
+
1036
+ const pending = this.pendingAlarms.slice();
1037
+ this.pendingAlarms = [];
1038
+
1039
+ pending.forEach((item) => {
1040
+ if (this.nodeEntries.has(item.path)) {
1041
+ return;
1042
+ }
1043
+
1044
+ this.addAlarm(item.parentNode, item.alarmConfig, item.parentPath, item.relationship);
1045
+ });
1046
+
1047
+ if (this.pendingAlarms.length) {
1048
+ const unresolved = this.pendingAlarms.map((item) => item.path).join(", ");
1049
+ this.pendingAlarms = [];
1050
+ throw new Error("Unresolved alarms after build: " + unresolved);
1051
+ }
1052
+ }
1053
+
1054
+
1055
+
1056
+
1057
+ addFolder(parentNode, folderConfig, parentPath, relationship, pathOverride) {
1058
+ const namespace = this.getNamespaceForConfig(folderConfig);
1059
+
1060
+ const addressSpace = this.server.engine.addressSpace;
1061
+ const serverNode = addressSpace.rootFolder.objects.server;
1062
+
1063
+ const folderName = folderConfig.name;
1064
+ const nextPath = pathOverride || this.buildPath(parentPath, folderName);
1065
+ const options = {
1066
+ browseName: folderConfig.displayName || folderName,
1067
+ displayName: folderConfig.displayName || folderName,
1068
+ description: folderConfig.description || "",
1069
+ nodeId: this.resolveNodeId(folderConfig, nextPath, namespace),
1070
+ rolePermissions: this.buildRolePermissions("folder", folderConfig),
1071
+ typeDefinition: "FolderType",
1072
+ eventSourceOf: serverNode,
1073
+ eventNotifier: 1 //enabled_events
1074
+ };
1075
+
1076
+ if (relationship === "organizedBy") {
1077
+ options.organizedBy = parentNode;
1078
+ } else {
1079
+ options.componentOf = parentNode;
1080
+ }
1081
+
1082
+ if (this.isObjectTypePath(parentPath)) {
1083
+ options.modellingRule = "Mandatory";
1084
+ }
1085
+
1086
+ const folderNode = namespace.addObject(options);
1087
+ this.registerNodeEntry("folder", nextPath, parentPath, relationship, folderConfig, folderNode, namespace);
1088
+ }
1089
+
1090
+ resolveDataType(type) {
1091
+ if (DATA_TYPE_MAP[type]) return DATA_TYPE_MAP[type];
1092
+ if (this.enumerationStore && this.enumerationStore.has(type)) {
1093
+ return this.enumerationStore.get(type).node.nodeId;
1094
+ }
1095
+ return type;
1096
+ }
1097
+
1098
+ addVariable(parentNode, variableConfig, parentPath, pathOverride) {
1099
+ const namespace = this.getNamespaceForConfig(variableConfig);
1100
+ const name = variableConfig.name;
1101
+ const type = variableConfig.type;
1102
+ const access = variableConfig.access;
1103
+ const path = pathOverride || this.buildPath(parentPath, name);
1104
+ const nodeId = this.resolveNodeId(variableConfig, path, namespace);
1105
+ const browseName = variableConfig.displayName || name;
1106
+ const state = {
1107
+ type,
1108
+ access,
1109
+ isArray: this.isArrayValue(variableConfig.value),
1110
+ currentValue: this.coerceValue(variableConfig.value, type, this.isArrayValue(variableConfig.value))
1111
+ };
1112
+
1113
+ const variableNode = namespace.addVariable({
1114
+ componentOf: parentNode,
1115
+ browseName,
1116
+ displayName: browseName,
1117
+ description: variableConfig.description || "",
1118
+ nodeId,
1119
+ rolePermissions: this.buildRolePermissions("variable", variableConfig),
1120
+ dataType: this.resolveDataType(type),
1121
+ modellingRule: this.isObjectTypePath(parentPath) ? "Mandatory" : undefined,
1122
+ valueRank: state.isArray ? 1 : -1,
1123
+ accessLevel: access === "readwrite" ? "CurrentRead | CurrentWrite" : "CurrentRead",
1124
+ userAccessLevel: access === "readwrite" ? "CurrentRead | CurrentWrite" : "CurrentRead",
1125
+ minimumSamplingInterval: 500,
1126
+ value: {
1127
+ get: () => {
1128
+ this.emitTagAccess("read", {
1129
+ path,
1130
+ nodeID: nodeId,
1131
+ browseName,
1132
+ dataType: state.type,
1133
+ value: state.currentValue
1134
+ });
1135
+
1136
+ let val = state.currentValue;
1137
+ if (state.type === "Int64" || state.type === "UInt64") {
1138
+ const bigIntToInt64Array = (v) => {
1139
+ let bigintVal;
1140
+ try {
1141
+ bigintVal = BigInt(v);
1142
+ } catch (e) {
1143
+ bigintVal = 0n;
1144
+ }
1145
+ const mask = 0xFFFFFFFFFFFFFFFFn;
1146
+ bigintVal = bigintVal & mask;
1147
+
1148
+ const high = Number(bigintVal >> 32n);
1149
+ const low = Number(bigintVal & 0xFFFFFFFFn);
1150
+ return [high, low];
1151
+ };
1152
+
1153
+ if (state.isArray) {
1154
+ val = Array.isArray(val) ? val.map(bigIntToInt64Array) : [bigIntToInt64Array(val)];
1155
+ } else {
1156
+ val = bigIntToInt64Array(val);
1157
+ }
1158
+ }
1159
+
1160
+ const variantOptions = {
1161
+ dataType: DATA_TYPE_MAP[state.type] || DataType.Int32,
1162
+ value: val
1163
+ };
1164
+
1165
+ // ByteString nunca e array - Buffer nao deve ser VariantArrayType.Array
1166
+ if (state.type !== "ByteString" && this.isArrayValue(state.currentValue)) {
1167
+ variantOptions.arrayType = VariantArrayType.Array;
1168
+ } else if (state.type === "Int64" || state.type === "UInt64") {
1169
+ variantOptions.arrayType = VariantArrayType.Scalar;
1170
+ }
1171
+
1172
+ return new Variant(variantOptions);
1173
+ },
1174
+ set: (variant) => {
1175
+ if (state.access !== "readwrite") {
1176
+ return StatusCodes.BadNotWritable;
1177
+ }
1178
+
1179
+ try {
1180
+ state.currentValue = this.coerceValue(variant.value, state.type, state.isArray);
1181
+ this.emitTagAccess("write", {
1182
+ path,
1183
+ nodeID: nodeId,
1184
+ browseName,
1185
+ dataType: state.type,
1186
+ value: state.currentValue
1187
+ });
1188
+
1189
+ const alarm = this.variableStore.get(path).alarm
1190
+ this.addressSpaceAlarm.checkAlarm(alarm, variant.value)
1191
+
1192
+
1193
+
1194
+ return StatusCodes.Good;
1195
+ } catch (error) {
1196
+ console.error("addVariable")
1197
+ console.error(error)
1198
+ // this.node.warn("Rejected OPC UA write for " + path + ": " + error.message);
1199
+ return StatusCodes.BadTypeMismatch;
1200
+ }
1201
+ }
1202
+ }
1203
+ });
1204
+
1205
+ this.registerNodeEntry("variable", path, parentPath, "componentOf", variableConfig, variableNode, namespace);
1206
+ const record = {
1207
+ node: variableNode,
1208
+ path: path,
1209
+ nodeId: nodeId,
1210
+ nodeIdKey: this.normalizeNodeIdKey(nodeId),
1211
+ type: state.type,
1212
+ isArray: state.isArray,
1213
+ getValue: () => state.currentValue,
1214
+ setRuntimeValue: (nextValue) => {
1215
+ state.currentValue = this.coerceValue(nextValue, state.type, state.isArray);
1216
+ return state.currentValue;
1217
+ },
1218
+ setValue: (nextValue) => {
1219
+ if (state.access !== "readwrite") {
1220
+ throw new Error("Tag is read-only: " + path);
1221
+ }
1222
+
1223
+ state.currentValue = this.coerceValue(nextValue, state.type, state.isArray);
1224
+
1225
+ const alarm = this.variableStore.get(path).alarm
1226
+ this.addressSpaceAlarm.checkAlarm(alarm, state.currentValue)
1227
+
1228
+ return state.currentValue;
1229
+ }
1230
+ };
1231
+ this.variableStore.set(path, record);
1232
+ this.variableNodeIdStore.set(record.nodeIdKey, record);
1233
+ }
1234
+
1235
+
1236
+
1237
+
1238
+
1239
+ addMethod(parentNode, methodConfig, parentPath, pathOverride) {
1240
+ const namespace = this.getNamespaceForConfig(methodConfig);
1241
+ const methodName = methodConfig.name;
1242
+ const path = pathOverride || this.buildPath(parentPath, methodName);
1243
+ const nodeId = this.resolveNodeId(methodConfig, path, namespace)
1244
+ const methodNode = namespace.addMethod(parentNode, {
1245
+ browseName: methodConfig.displayName || methodName,
1246
+ displayName: methodConfig.displayName || methodName,
1247
+ description: { text: methodConfig.description || "" },
1248
+ nodeId: nodeId,
1249
+ rolePermissions: this.buildRolePermissions("method", methodConfig),
1250
+ modellingRule: this.isObjectTypePath(parentPath) ? "Mandatory" : undefined,
1251
+ inputArguments: methodConfig.inputs.map((arg) => ({
1252
+ name: arg.name,
1253
+ description: { text: arg.description || "" },
1254
+ dataType: DATA_TYPE_MAP[arg.type]
1255
+ })),
1256
+ outputArguments: methodConfig.outputs.map((arg) => ({
1257
+ name: arg.name,
1258
+ description: { text: arg.description || "" },
1259
+ dataType: DATA_TYPE_MAP[arg.type]
1260
+ }))
1261
+ });
1262
+
1263
+ methodNode.bindMethod((inputArguments, context, callback) => {
1264
+ const callId = Date.now() + "_" + Math.random();
1265
+
1266
+ this.registry.emitMethodCall({
1267
+ methodName: methodConfig.name,
1268
+ nodeId: nodeId,
1269
+ callId,
1270
+ inputArguments,
1271
+ outputArguments: methodConfig.outputs.map((arg) => ({
1272
+ name: arg.name,
1273
+ description: { text: arg.description || "" },
1274
+ dataType: DATA_TYPE_MAP[arg.type]
1275
+ })),
1276
+ serverName: this.serverName
1277
+ });
1278
+
1279
+ this.registry.waitForMethodResponse(callId)
1280
+ .then((outputs) => {
1281
+ callback(null, {
1282
+ statusCode: StatusCodes.Good,
1283
+ outputArguments: outputs.map((output) => new Variant(output))
1284
+ });
1285
+ })
1286
+ .catch(() => {
1287
+ callback(null, {
1288
+ statusCode: StatusCodes.BadInternalError
1289
+ });
1290
+ });
1291
+ });
1292
+
1293
+ this.registerNodeEntry("method", path, parentPath, "componentOf", methodConfig, methodNode, namespace);
1294
+ }
1295
+
1296
+ addAlarmPlaceholder(parentPath, alarmConfig) {
1297
+ const path = this.buildPath(parentPath, alarmConfig.name);
1298
+ this.node.debug("Alarm definition received but not implemented yet: " + path + " (" + alarmConfig.type + ")");
1299
+ }
1300
+
1301
+ registerNodeEntry(kind, path, parentPath, relationship, config, node, namespace) {
1302
+ this.nodeEntries.set(path, {
1303
+ kind,
1304
+ path,
1305
+ parentPath,
1306
+ relationship,
1307
+ config,
1308
+ signature: this.buildSignature(kind, config, parentPath, relationship),
1309
+ node,
1310
+ namespace
1311
+ });
1312
+ }
1313
+
1314
+ buildPath(parentPath, name) {
1315
+ return parentPath ? parentPath + "." + name : name;
1316
+ }
1317
+
1318
+ buildObjectTypePath(name) {
1319
+ return "__objectTypes." + name;
1320
+ }
1321
+
1322
+ buildEnumerationPath(name) {
1323
+ return "__enumerations." + name;
1324
+ }
1325
+
1326
+ buildCollectionPath(parentPath, collectionName, name) {
1327
+
1328
+ // return parentPath ? parentPath + "." + collectionName + "." + name : collectionName + "." + name;
1329
+ //change nodeId path
1330
+ return parentPath ? parentPath + "." + name : name + "." + name;
1331
+
1332
+ }
1333
+
1334
+ isObjectTypePath(path) {
1335
+ return String(path || "").indexOf("__objectTypes.") === 0;
1336
+ }
1337
+
1338
+ buildStableNodeId(path, namespace) {
1339
+ return "ns=" + namespace.index + ";s=" + sanitizeNodeIdPath(path);
1340
+ }
1341
+
1342
+ resolveNodeId(config, path, namespace) {
1343
+ const customNodeId = config && typeof config.nodeId === "string" ? config.nodeId.trim() : "";
1344
+ return customNodeId || this.buildStableNodeId(path, namespace);
1345
+ }
1346
+
1347
+ resolveNamespaceId(config) {
1348
+ const namespaceId = config && config.namespaceId !== undefined ? Number(config.namespaceId) : 2;
1349
+ return Number.isInteger(namespaceId) && namespaceId >= 2 ? namespaceId : 2;
1350
+ }
1351
+
1352
+ getNamespaceForConfig(config) {
1353
+ const namespaceId = this.resolveNamespaceId(config);
1354
+ if (!this.namespaces.has(namespaceId)) {
1355
+ throw new Error("Namespace " + namespaceId + " is not available");
1356
+ }
1357
+
1358
+ return this.namespaces.get(namespaceId);
1359
+ }
1360
+
1361
+ emitTagAccess(operation, details) {
1362
+ let val = details.value;
1363
+ if (details.dataType === "Int64" || details.dataType === "UInt64") {
1364
+ const convertToNumber = (v) => {
1365
+ const num = Number(v);
1366
+ return Number.isFinite(num) ? num : v;
1367
+ };
1368
+ const isArray = this.isArrayValue(val);
1369
+ if (isArray) {
1370
+ const items = this.extractArrayItems(val);
1371
+ val = Array.isArray(items) ? items.map(convertToNumber) : convertToNumber(val);
1372
+ } else {
1373
+ val = convertToNumber(val);
1374
+ }
1375
+ }
1376
+
1377
+ this.registry.emitTagAccess({
1378
+ operation,
1379
+ serverId: this.node.id,
1380
+ serverNodeName: this.node.name || "",
1381
+ serverName: this.serverName,
1382
+ timestamp: new Date().toISOString(),
1383
+ path: details.path,
1384
+ nodeID: details.nodeID,
1385
+ browseName: details.browseName,
1386
+ dataType: details.dataType,
1387
+ value: val
1388
+ });
1389
+ }
1390
+
1391
+ getVariableRecord(identifierType, identifier) {
1392
+ if (identifierType === "nodeId") {
1393
+ return this.getVariableRecordByNodeId(identifier);
1394
+ }
1395
+
1396
+ return this.getVariableRecordByPath(identifier);
1397
+ }
1398
+
1399
+ getVariableRecordByPath(path) {
1400
+ if (!this.variableStore.has(path)) {
1401
+ throw new Error("Unknown path: " + path);
1402
+ }
1403
+
1404
+ return this.variableStore.get(path);
1405
+ }
1406
+
1407
+ getVariableRecordByNodeId(nodeId) {
1408
+ const key = this.normalizeNodeIdKey(nodeId);
1409
+ if (!this.variableNodeIdStore.has(key)) {
1410
+ throw new Error("Unknown nodeId: " + nodeId);
1411
+ }
1412
+
1413
+ return this.variableNodeIdStore.get(key);
1414
+ }
1415
+
1416
+ normalizeNodeIdKey(nodeId) {
1417
+ return coerceNodeId(nodeId).toString();
1418
+ }
1419
+
1420
+ resolveAlarmVariableRecord(parentPath, parentNode, variableNodeId) {
1421
+ const reference = String(variableNodeId || "").trim();
1422
+ if (!reference) {
1423
+ throw new Error("Alarm variableNodeId is required");
1424
+ }
1425
+
1426
+ if (reference.indexOf(".") === 0) {
1427
+ const relativeReference = this.resolveObjectTypeRelativeReference(parentPath, reference);
1428
+
1429
+ //not work
1430
+ var corrente = parentNode.getComponentByName("corrent")
1431
+
1432
+ return {
1433
+ node: corrente
1434
+ }
1435
+ if (this.variableStore.has(relativeReference)) {
1436
+ return this.variableStore.get(relativeReference);
1437
+ }
1438
+ }
1439
+
1440
+ if (this.variableStore.has(reference)) {
1441
+ return this.variableStore.get(reference);
1442
+ }
1443
+
1444
+ try {
1445
+ return this.getVariableRecordByNodeId(reference);
1446
+ } catch (error) {
1447
+ // ignore and continue with path heuristics
1448
+ }
1449
+
1450
+ const stringNodeIdPath = this.extractStringNodeIdPath(reference);
1451
+ if (stringNodeIdPath) {
1452
+ if (this.variableStore.has(stringNodeIdPath)) {
1453
+ return this.variableStore.get(stringNodeIdPath);
1454
+ }
1455
+
1456
+ const relativeStringNodeIdPath = this.buildPath(parentPath, stringNodeIdPath);
1457
+ if (this.variableStore.has(relativeStringNodeIdPath)) {
1458
+ return this.variableStore.get(relativeStringNodeIdPath);
1459
+ }
1460
+ }
1461
+
1462
+ const relativePath = this.buildPath(parentPath, reference);
1463
+ if (this.variableStore.has(relativePath)) {
1464
+ return this.variableStore.get(relativePath);
1465
+ }
1466
+
1467
+ const suffixMatches = Array.from(this.variableStore.entries())
1468
+ .filter(([path]) => {
1469
+ return path === reference ||
1470
+ path.indexOf("." + reference) !== -1 ||
1471
+ (stringNodeIdPath && (path === stringNodeIdPath || path.indexOf("." + stringNodeIdPath) !== -1));
1472
+ });
1473
+
1474
+ if (suffixMatches.length === 1) {
1475
+ return suffixMatches[0][1];
1476
+ }
1477
+
1478
+ throw new Error("Unknown alarm variable reference: " + reference);
1479
+ }
1480
+
1481
+ extractStringNodeIdPath(reference) {
1482
+ const match = /^ns=\d+;s=(.+)$/.exec(String(reference || "").trim());
1483
+ return match ? match[1] : "";
1484
+ }
1485
+
1486
+ resolveObjectTypeRelativeReference(rootInstancePath, reference) {
1487
+ const tokens = String(reference || "")
1488
+ .trim()
1489
+ .replace(/^\.+/, "")
1490
+ .split(".")
1491
+ .filter((token) => token !== "")
1492
+ .map((token) => this.normalizeCollectionToken(token));
1493
+
1494
+ if (!tokens.length) {
1495
+ return rootInstancePath;
1496
+ }
1497
+
1498
+ return rootInstancePath + "." + tokens.join(".");
1499
+ }
1500
+
1501
+ normalizeCollectionToken(token) {
1502
+ const normalized = String(token || "").trim().toLowerCase();
1503
+ const aliases = {
1504
+ variaveis: "variables",
1505
+ variables: "variables",
1506
+ objetos: "objects",
1507
+ objects: "objects",
1508
+ pastas: "folders",
1509
+ folders: "folders",
1510
+ metodos: "methods",
1511
+ methods: "methods",
1512
+ alarmes: "alarms",
1513
+ alarms: "alarms",
1514
+ objecttypes: "objectsTypes",
1515
+ objectstypes: "objectsTypes",
1516
+ tiposobjetos: "objectsTypes"
1517
+ };
1518
+
1519
+ return aliases[normalized] || token;
1520
+ }
1521
+
1522
+ coerceValue(value, type, expectArray) {
1523
+ // ByteString nunca e array - Buffer/Uint8Array tratados diretamente
1524
+ if (type === "ByteString") {
1525
+ return this.coerceScalarValue(value, type);
1526
+ }
1527
+
1528
+ if (expectArray) {
1529
+ const items = this.extractArrayItems(value);
1530
+ if (!items) {
1531
+ throw new Error("Expected array value for type " + type);
1532
+ }
1533
+
1534
+ return items.map((item) => this.coerceScalarValue(item, type));
1535
+ }
1536
+
1537
+ if (typeof value === "string") {
1538
+ const trimmed = value.trim();
1539
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
1540
+ try {
1541
+ const parsed = JSON.parse(trimmed);
1542
+ if (Array.isArray(parsed)) {
1543
+ throw new Error("Expected scalar value for type " + type + " but received array");
1544
+ }
1545
+ } catch (error) {
1546
+ if (error.message.indexOf("Expected scalar value") === 0) {
1547
+ throw error;
1548
+ }
1549
+
1550
+ throw new Error("Invalid array value for type " + type + ": " + error.message);
1551
+ }
1552
+ }
1553
+ }
1554
+
1555
+ if (this.extractArrayItems(value)) {
1556
+ if ((type === "Int64" || type === "UInt64") && Array.isArray(value) && value.length === 2 && typeof value[0] === "number" && typeof value[1] === "number") {
1557
+ // Do not throw, this is a standard scalar Int64/UInt64 represented as [high, low]
1558
+ } else {
1559
+ throw new Error("Expected scalar value for type " + type + " but received array");
1560
+ }
1561
+ }
1562
+
1563
+ return this.coerceScalarValue(value, type);
1564
+ }
1565
+
1566
+ coerceScalarValue(value, type) {
1567
+
1568
+ if (type === "Int16") {
1569
+ const parsed = Number(value);
1570
+ if (!Number.isFinite(parsed)) {
1571
+ return 0;
1572
+ }
1573
+ return Math.trunc(parsed);
1574
+ }
1575
+
1576
+
1577
+ if (type === "Int32") {
1578
+ const parsed = Number(value);
1579
+ if (!Number.isFinite(parsed)) {
1580
+ return 0;
1581
+ }
1582
+ return Math.trunc(parsed);
1583
+ }
1584
+
1585
+ if (type === "Int64") {
1586
+ const minVal = -9223372036854775808n;
1587
+ const maxVal = 9223372036854775807n;
1588
+ if (Array.isArray(value) && value.length === 2) {
1589
+ try {
1590
+ const h = BigInt(value[0]);
1591
+ const l = BigInt(value[1]);
1592
+ const signMask = 1n << 31n;
1593
+ const shiftHigh = 1n << 32n;
1594
+ let bigintVal;
1595
+ if ((h & signMask) === signMask) {
1596
+ bigintVal = (h & ~signMask) * shiftHigh + l - 0x8000000000000000n;
1597
+ } else {
1598
+ bigintVal = h * shiftHigh + l;
1599
+ }
1600
+ if (bigintVal < minVal) bigintVal = minVal;
1601
+ else if (bigintVal > maxVal) bigintVal = maxVal;
1602
+ return String(bigintVal);
1603
+ } catch (error) {
1604
+ return "0";
1605
+ }
1606
+ }
1607
+ try {
1608
+ let bigintVal = BigInt(value);
1609
+ if (bigintVal < minVal) bigintVal = minVal;
1610
+ else if (bigintVal > maxVal) bigintVal = maxVal;
1611
+ return String(bigintVal);
1612
+ } catch (error) {
1613
+ const parsed = Number(value);
1614
+ if (Number.isFinite(parsed)) {
1615
+ try {
1616
+ let bigintVal = BigInt(Math.trunc(parsed));
1617
+ if (bigintVal < minVal) bigintVal = minVal;
1618
+ else if (bigintVal > maxVal) bigintVal = maxVal;
1619
+ return String(bigintVal);
1620
+ } catch (e2) {
1621
+ return "0";
1622
+ }
1623
+ }
1624
+ return "0";
1625
+ }
1626
+ }
1627
+
1628
+ if (type === "UInt64") {
1629
+ const minVal = 0n;
1630
+ const maxVal = 18446744073709551615n;
1631
+ if (Array.isArray(value) && value.length === 2) {
1632
+ try {
1633
+ const h = BigInt(value[0]);
1634
+ const l = BigInt(value[1]);
1635
+ const shiftHigh = 1n << 32n;
1636
+ let bigintVal = h * shiftHigh + l;
1637
+ if (bigintVal < minVal) bigintVal = minVal;
1638
+ else if (bigintVal > maxVal) bigintVal = maxVal;
1639
+ return String(bigintVal);
1640
+ } catch (error) {
1641
+ return "0";
1642
+ }
1643
+ }
1644
+ try {
1645
+ let bigintVal = BigInt(value);
1646
+ if (bigintVal < minVal) bigintVal = minVal;
1647
+ else if (bigintVal > maxVal) bigintVal = maxVal;
1648
+ return String(bigintVal);
1649
+ } catch (error) {
1650
+ const parsed = Number(value);
1651
+ if (Number.isFinite(parsed)) {
1652
+ try {
1653
+ let bigintVal = BigInt(Math.trunc(parsed));
1654
+ if (bigintVal < minVal) bigintVal = minVal;
1655
+ else if (bigintVal > maxVal) bigintVal = maxVal;
1656
+ return String(bigintVal);
1657
+ } catch (e2) {
1658
+ return "0";
1659
+ }
1660
+ }
1661
+ return "0";
1662
+ }
1663
+ }
1664
+
1665
+ if (type === "Float") {
1666
+ const parsed = Number(value);
1667
+ if (!Number.isFinite(parsed)) {
1668
+ return 0;
1669
+ }
1670
+ return parsed;
1671
+ }
1672
+
1673
+ if (type === "Boolean") {
1674
+ if (typeof value === "string") {
1675
+ const normalized = value.trim().toLowerCase();
1676
+ if (normalized === "false" || normalized === "0" || normalized === "") {
1677
+ return false;
1678
+ }
1679
+ if (normalized === "true" || normalized === "1") {
1680
+ return true;
1681
+ }
1682
+ }
1683
+
1684
+ return Boolean(value);
1685
+ }
1686
+
1687
+ if (type === "ByteString") {
1688
+ if (Buffer.isBuffer(value)) {
1689
+ return value;
1690
+ }
1691
+ if (value instanceof Uint8Array) {
1692
+ return Buffer.from(value);
1693
+ }
1694
+ if (typeof value === "string") {
1695
+ return Buffer.from(value, "base64");
1696
+ }
1697
+ if (Array.isArray(value)) {
1698
+ return Buffer.from(value);
1699
+ }
1700
+ return Buffer.alloc(0);
1701
+ }
1702
+
1703
+ return value === undefined || value === null ? "" : String(value);
1704
+ }
1705
+
1706
+ isArrayValue(value) {
1707
+ return this.extractArrayItems(value) !== null;
1708
+ }
1709
+
1710
+ extractArrayItems(value) {
1711
+ if (Array.isArray(value)) {
1712
+ return value;
1713
+ }
1714
+
1715
+ if (typeof value === "string") {
1716
+ const trimmed = value.trim();
1717
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
1718
+ const parsed = JSON.parse(trimmed);
1719
+ return Array.isArray(parsed) ? parsed : null;
1720
+ }
1721
+ return null;
1722
+ }
1723
+
1724
+ if (ArrayBuffer.isView(value)) {
1725
+ return Array.from(value);
1726
+ }
1727
+
1728
+ return null;
1729
+ }
1730
+ }
1731
+
1732
+ function collapsePaths(paths) {
1733
+ return Array.from(new Set(paths))
1734
+ .sort(comparePathDepthAsc)
1735
+ .filter((path, index, ordered) => {
1736
+ for (let current = 0; current < index; current += 1) {
1737
+ if (isSamePathOrDescendant(path, ordered[current])) {
1738
+ return false;
1739
+ }
1740
+ }
1741
+ return true;
1742
+ });
1743
+ }
1744
+
1745
+ function isSamePathOrDescendant(path, rootPath) {
1746
+ return path === rootPath || path.indexOf(rootPath + ".") === 0;
1747
+ }
1748
+
1749
+ function comparePathDepthAsc(left, right) {
1750
+ return pathDepth(left) - pathDepth(right);
1751
+ }
1752
+
1753
+ function comparePathDepthDesc(left, right) {
1754
+ return pathDepth(right) - pathDepth(left);
1755
+ }
1756
+
1757
+ function pathDepth(path) {
1758
+ return String(path || "").split(".").length;
1759
+ }
1760
+
1761
+ function compareEntryCreationOrder(left, right) {
1762
+ const depthDelta = pathDepth(left.path) - pathDepth(right.path);
1763
+ if (depthDelta !== 0) {
1764
+ return depthDelta;
1765
+ }
1766
+
1767
+ return kindRank(left.kind) - kindRank(right.kind);
1768
+ }
1769
+
1770
+ function kindRank(kind) {
1771
+ if (kind === "objectTypeDefinition" || kind === "enumeration") {
1772
+ return -1;
1773
+ }
1774
+ if (kind === "folder") {
1775
+ return 0;
1776
+ }
1777
+ if (kind === "object") {
1778
+ return 1;
1779
+ }
1780
+ if (kind === "objectTypeInstance") {
1781
+ return 1;
1782
+ }
1783
+ if (kind === "variable") {
1784
+ return 2;
1785
+ }
1786
+ if (kind === "method") {
1787
+ return 3;
1788
+ }
1789
+ if (kind === "alarm") {
1790
+ return 4;
1791
+ }
1792
+ return 10;
1793
+ }
1794
+
1795
+ module.exports = {
1796
+ OpcUaAddressSpaceBuilder
1797
+ };