@vitormnm/node-red-simple-opcua 1.0.2 → 1.1.0
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.
- package/client/lib/opcua-client-browser.js +291 -0
- package/client/lib/opcua-client-read-service.js +16 -0
- package/client/lib/opcua-client-subscription-id-service.js +25 -0
- package/client/lib/opcua-client-subscription-service.js +171 -0
- package/client/lib/opcua-client-write-service.js +53 -0
- package/client/opcua-client-config.html +80 -0
- package/client/opcua-client-config.js +159 -0
- package/client/opcua-client-utils.js +320 -0
- package/client/opcua-client.html +1225 -0
- package/client/opcua-client.js +380 -0
- package/object.json +65 -0
- package/package.json +4 -5
- package/server/lib/opcua-address-space-alarm.js +341 -0
- package/server/lib/opcua-address-space-builder.js +1456 -0
- package/server/lib/opcua-config.js +546 -0
- package/server/lib/opcua-constants.js +109 -0
- package/server/lib/opcua-server-events-child.js +140 -0
- package/server/lib/opcua-server-methods.js +198 -0
- package/server/lib/opcua-server-runtime-child.js +729 -0
- package/server/lib/opcua-server-runtime.js +311 -0
- package/server/lib/opcua-server-status-child.js +188 -0
- package/server/lib/server-node-utils.js +16 -0
- package/server/opcua-server-io.html +347 -0
- package/server/opcua-server-io.js +463 -0
- package/server/opcua-server-registry.js +270 -0
- package/server/opcua-server.css +265 -0
- package/server/opcua-server.html +1548 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
const registry = require("../opcua-server-registry");
|
|
5
|
+
const { resolveRegisteredServer } = require("./server-node-utils");
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
function eventsServer(node, rootNode, nodeId) {
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
// 🔥 ALTERADO: usar Map para garantir unicidade por nodeID
|
|
15
|
+
node.queue = {
|
|
16
|
+
read: new Map(),
|
|
17
|
+
write: new Map(),
|
|
18
|
+
alarm: new Map()
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
if (node.flushTimer) {
|
|
22
|
+
clearInterval(node.flushTimer);
|
|
23
|
+
node.flushTimer = null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
node.flushTimer = setInterval(() => {
|
|
28
|
+
flushQueue(node, nodeId);
|
|
29
|
+
|
|
30
|
+
}, rootNode.intervalMs);
|
|
31
|
+
|
|
32
|
+
///rootNode.intervalMs
|
|
33
|
+
node.enqueueAccessEvent = function (event) {
|
|
34
|
+
if (!matchesServer(node.serverRef, event)) {
|
|
35
|
+
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (event.operation === "read") {
|
|
39
|
+
upsertEvent(node.queue.read, event);
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (event.operation === "write") {
|
|
44
|
+
upsertEvent(node.queue.write, event);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (event.operation === "alarm") {
|
|
48
|
+
upsertEvent(node.queue.alarm, event);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
registry.registerAccessListener(node.id, node);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
function flushQueue(node, nodeId) {
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
// ALTERADO: usar size (Map)
|
|
64
|
+
if (!node.queue.read.size && !node.queue.write.size) {
|
|
65
|
+
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
const payload = {
|
|
70
|
+
serverRef: node.serverRef || "",
|
|
71
|
+
intervalMs: node.intervalMs,
|
|
72
|
+
timestamp: new Date().toISOString(),
|
|
73
|
+
|
|
74
|
+
// 🔥 ALTERADO: converter Map -> Array
|
|
75
|
+
read: Array.from(node.queue.read.values()),
|
|
76
|
+
write: Array.from(node.queue.write.values()),
|
|
77
|
+
alarm: Array.from(node.queue.alarm.values())
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// ALTERADO: limpar Maps
|
|
81
|
+
node.queue.read.clear();
|
|
82
|
+
node.queue.write.clear();
|
|
83
|
+
node.queue.alarm.clear();
|
|
84
|
+
|
|
85
|
+
process.send({
|
|
86
|
+
type: "send",
|
|
87
|
+
data: {
|
|
88
|
+
payload,
|
|
89
|
+
opcua: {
|
|
90
|
+
server: payload.serverRef
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
nodeId: nodeId
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
process.send({
|
|
97
|
+
type: "status",
|
|
98
|
+
data: {
|
|
99
|
+
fill: "green",
|
|
100
|
+
shape: "dot",
|
|
101
|
+
text: "read " + payload.read.length + " write " + payload.write.length
|
|
102
|
+
},
|
|
103
|
+
nodeId: nodeId
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
function upsertEvent(map, event) {
|
|
113
|
+
const key = String(event.nodeID || "").trim();
|
|
114
|
+
if (!key) return;
|
|
115
|
+
|
|
116
|
+
map.set(key, event); // sobrescreve automaticamente
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function matchesServer(serverRef, event) {
|
|
120
|
+
if (!serverRef) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const normalizedRef = String(serverRef).trim();
|
|
125
|
+
return normalizedRef === String(event.serverId || "").trim()
|
|
126
|
+
|| normalizedRef === String(event.serverNodeName || "").trim()
|
|
127
|
+
|| normalizedRef === String(event.serverName || "").trim();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function normalizeInterval(value) {
|
|
131
|
+
const interval = Number(value);
|
|
132
|
+
if (!Number.isFinite(interval) || interval <= 0) {
|
|
133
|
+
return 500;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return Math.trunc(interval);
|
|
137
|
+
}
|
|
138
|
+
module.exports = {
|
|
139
|
+
"eventsServer": eventsServer
|
|
140
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
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
|
+
|
|
14
|
+
const { OpcUaAddressSpaceAlarm } = require("./opcua-address-space-alarm")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class OpcUaServerMethods {
|
|
18
|
+
|
|
19
|
+
constructor(options) {
|
|
20
|
+
this.addressSpace = options.addressSpace
|
|
21
|
+
this.node = options.addressSpace
|
|
22
|
+
this.registry = options.registry
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
//Use OpcUaAddressSpaceAlarm to set an alarm message to be stored in the history.
|
|
26
|
+
this.addressSpaceAlarm = new OpcUaAddressSpaceAlarm({
|
|
27
|
+
registry: this.registry,
|
|
28
|
+
node: this.node
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
start() {
|
|
36
|
+
this.acknowledgeTypeMethod()
|
|
37
|
+
this.confirmTypeMethod()
|
|
38
|
+
this.conditionRefreshMethod()
|
|
39
|
+
this.addCommentMethod()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
confirmTypeMethod() {
|
|
43
|
+
const confirmTypeMethod = this.addressSpace.findNode("ns=0;i=9113");
|
|
44
|
+
confirmTypeMethod.bindMethod(
|
|
45
|
+
(inputArguments, context, callback) => {
|
|
46
|
+
const eventId = inputArguments[0].value;
|
|
47
|
+
const comment = inputArguments[1].value;
|
|
48
|
+
const alarm = context.object;
|
|
49
|
+
|
|
50
|
+
// severity atual
|
|
51
|
+
const severity = alarm.severity.readValue().value.value;
|
|
52
|
+
|
|
53
|
+
// message atual
|
|
54
|
+
const message = alarm.message.readValue().value.value.text;
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if (alarm.activeState.getValue()) {
|
|
58
|
+
|
|
59
|
+
alarm.confirmedState.setValue(true);
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
this.addressSpaceAlarm.raiseNewConditionAlarm(alarm, message, severity, true)
|
|
63
|
+
alarm.raiseNewCondition({
|
|
64
|
+
message: message,
|
|
65
|
+
severity: severity,
|
|
66
|
+
retain: true
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
} else {
|
|
71
|
+
alarm.confirmedState.setValue(true);
|
|
72
|
+
this.addressSpaceAlarm.raiseNewConditionAlarm(alarm, message, severity, false)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
callback(null, {
|
|
79
|
+
statusCode: StatusCodes.Good,
|
|
80
|
+
outputArguments: []
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
addCommentMethod() {
|
|
88
|
+
const addCommentMethod = this.addressSpace.findNode("ns=0;i=9029");
|
|
89
|
+
addCommentMethod.bindMethod(
|
|
90
|
+
(inputArguments, context, callback) => {
|
|
91
|
+
const eventId = inputArguments[0].value;
|
|
92
|
+
const comment = inputArguments[1].value;
|
|
93
|
+
const alarm = context.object;
|
|
94
|
+
|
|
95
|
+
// alarm.addComment(eventId, comment, context.session);
|
|
96
|
+
alarm.comment.setValueFromSource({
|
|
97
|
+
value: comment,
|
|
98
|
+
dataType: DataType.LocalizedText
|
|
99
|
+
});
|
|
100
|
+
callback(null, {
|
|
101
|
+
statusCode: StatusCodes.Good,
|
|
102
|
+
outputArguments: []
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
acknowledgeTypeMethod() {
|
|
109
|
+
const acknowledgeTypeMethod = this.addressSpace.findNode("ns=0;i=9111");
|
|
110
|
+
|
|
111
|
+
acknowledgeTypeMethod.bindMethod(
|
|
112
|
+
(inputArguments, context, callback) => {
|
|
113
|
+
const eventId = inputArguments[0].value;
|
|
114
|
+
const comment = inputArguments[1].value;
|
|
115
|
+
|
|
116
|
+
const alarm = context.object;
|
|
117
|
+
// severity atual
|
|
118
|
+
const severity = alarm.severity.readValue().value.value;
|
|
119
|
+
|
|
120
|
+
// message atual
|
|
121
|
+
const message = alarm.message.readValue().value.value.text;
|
|
122
|
+
|
|
123
|
+
alarm.ackedState.setValue(true);
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
alarm.confirmedState.setValue(false);
|
|
127
|
+
// IMPORTANTE: Para o cliente "ver", o alarme precisa gerar um evento de mudança
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
this.addressSpaceAlarm.raiseNewConditionAlarm(alarm, message, severity, true)
|
|
133
|
+
|
|
134
|
+
callback(null, {
|
|
135
|
+
statusCode: StatusCodes.Good,
|
|
136
|
+
outputArguments: []
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
conditionRefreshMethod() {
|
|
145
|
+
const conditionType = this.addressSpace.findObjectType("ConditionType");
|
|
146
|
+
|
|
147
|
+
const conditionRefreshMethod = conditionType.getMethodByName("ConditionRefresh");
|
|
148
|
+
|
|
149
|
+
conditionRefreshMethod.bindMethod(
|
|
150
|
+
(inputArguments, context, callback) => {
|
|
151
|
+
|
|
152
|
+
const subscriptionId = inputArguments[0].value;
|
|
153
|
+
|
|
154
|
+
var ActiveAlarms = this.registry.getActiveAlarms(this.node)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
ActiveAlarms.forEach(element => {
|
|
158
|
+
const alarmNode = element.alarmNode
|
|
159
|
+
|
|
160
|
+
alarmNode.raiseNewCondition({
|
|
161
|
+
message: element.message,
|
|
162
|
+
severity: element.severity,
|
|
163
|
+
retain: element.retain
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
// for (const [, state] of retainedAlarms) {
|
|
170
|
+
// const alarm = state.alarm;
|
|
171
|
+
|
|
172
|
+
// alarm.raiseNewCondition({
|
|
173
|
+
// message: state.message,
|
|
174
|
+
// severity: state.severity,
|
|
175
|
+
// retain: state.retain
|
|
176
|
+
// });
|
|
177
|
+
// }
|
|
178
|
+
|
|
179
|
+
callback(null, {
|
|
180
|
+
statusCode: StatusCodes.Good,
|
|
181
|
+
outputArguments: [{
|
|
182
|
+
dataType: DataType.String,
|
|
183
|
+
value: JSON.stringify(ActiveAlarms)
|
|
184
|
+
}]
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
// reenviar alarmes ativos
|
|
189
|
+
// aqui você percorre seus alarmes e dispara eventos novamente
|
|
190
|
+
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
module.exports = { OpcUaServerMethods }
|