@vitormnm/node-red-simple-opcua 1.0.0 → 1.0.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.
- package/examples/flows_simple_opc.json +1 -0
- package/package.json +8 -3
- package/client/lib/opcua-client-browser.js +0 -291
- package/client/lib/opcua-client-read-service.js +0 -16
- package/client/lib/opcua-client-subscription-id-service.js +0 -25
- package/client/lib/opcua-client-subscription-service.js +0 -171
- package/client/lib/opcua-client-write-service.js +0 -53
- package/client/opcua-client-config.html +0 -80
- package/client/opcua-client-config.js +0 -159
- package/client/opcua-client-utils.js +0 -320
- package/client/opcua-client.html +0 -1225
- package/client/opcua-client.js +0 -380
- package/object.json +0 -65
- package/server/lib/opcua-address-space-alarm.js +0 -341
- package/server/lib/opcua-address-space-builder.js +0 -1456
- package/server/lib/opcua-config.js +0 -543
- package/server/lib/opcua-constants.js +0 -106
- package/server/lib/opcua-server-events-child.js +0 -140
- package/server/lib/opcua-server-methods.js +0 -198
- package/server/lib/opcua-server-runtime-child.js +0 -729
- package/server/lib/opcua-server-runtime.js +0 -311
- package/server/lib/opcua-server-status-child.js +0 -188
- package/server/lib/server-node-utils.js +0 -16
- package/server/opcua-server-io.html +0 -347
- package/server/opcua-server-io.js +0 -463
- package/server/opcua-server-registry.js +0 -270
- package/server/opcua-server.css +0 -265
- package/server/opcua-server.html +0 -1548
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const {
|
|
4
|
-
ClientMonitoredItem,
|
|
5
|
-
ClientSubscription,
|
|
6
|
-
TimestampsToReturn,
|
|
7
|
-
AttributeIds,
|
|
8
|
-
constructEventFilter
|
|
9
|
-
} = require("node-opcua");
|
|
10
|
-
|
|
11
|
-
const {
|
|
12
|
-
dataValueToItemResult,
|
|
13
|
-
dataValueToItemResultEvent,
|
|
14
|
-
resolveName,
|
|
15
|
-
resolveNodeId,
|
|
16
|
-
statusCodeToString
|
|
17
|
-
} = require("../opcua-client-utils");
|
|
18
|
-
|
|
19
|
-
class OpcUaClientSubscriptionService {
|
|
20
|
-
async startDataSubscription(node, msg, itemsResolver) {
|
|
21
|
-
const items = itemsResolver.ensureClientItems(node, msg, "OPC UA subscription");
|
|
22
|
-
await this.stop(node);
|
|
23
|
-
|
|
24
|
-
const session = await node.connection.getSession();
|
|
25
|
-
const subscription = ClientSubscription.create(session, {
|
|
26
|
-
requestedPublishingInterval: node.publishingInterval,
|
|
27
|
-
requestedLifetimeCount: 60,
|
|
28
|
-
requestedMaxKeepAliveCount: 10,
|
|
29
|
-
maxNotificationsPerPublish: 100,
|
|
30
|
-
publishingEnabled: true,
|
|
31
|
-
priority: 1
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
subscription.on("error", (error) => {
|
|
35
|
-
node.status({ fill: "red", shape: "ring", text: "subscription error" });
|
|
36
|
-
node.error(error);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
node.subscription = subscription;
|
|
40
|
-
node.monitoredItems = items.map((item) => {
|
|
41
|
-
const monitoredItem = ClientMonitoredItem.create(
|
|
42
|
-
subscription,
|
|
43
|
-
{ nodeId: resolveNodeId(item) },
|
|
44
|
-
{
|
|
45
|
-
samplingInterval: node.samplingInterval,
|
|
46
|
-
discardOldest: true,
|
|
47
|
-
queueSize: 1
|
|
48
|
-
},
|
|
49
|
-
TimestampsToReturn.Both
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
monitoredItem.on("changed", (dataValue) => {
|
|
53
|
-
const payload = dataValueToItemResult(item, dataValue);
|
|
54
|
-
node.status({
|
|
55
|
-
fill: "blue",
|
|
56
|
-
shape: "dot",
|
|
57
|
-
text: resolveName(item, payload.nodeID) + " changed"
|
|
58
|
-
});
|
|
59
|
-
node.send({
|
|
60
|
-
topic: payload.name,
|
|
61
|
-
payload,
|
|
62
|
-
opcua: {
|
|
63
|
-
mode: "subscription",
|
|
64
|
-
nodeID: payload.nodeID,
|
|
65
|
-
status: payload.status
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
monitoredItem.on("err", (message) => {
|
|
71
|
-
node.status({ fill: "red", shape: "ring", text: statusCodeToString(message) });
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
return monitoredItem;
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
return items;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async startEventSubscription(node, msg, itemsResolver) {
|
|
81
|
-
const items = itemsResolver.ensureClientItems(node, msg, "OPC UA subscription");
|
|
82
|
-
await this.stop(node);
|
|
83
|
-
|
|
84
|
-
const session = await node.connection.getSession();
|
|
85
|
-
const subscription = ClientSubscription.create(session, {
|
|
86
|
-
requestedPublishingInterval: node.publishingInterval,
|
|
87
|
-
requestedLifetimeCount: 60,
|
|
88
|
-
requestedMaxKeepAliveCount: 10,
|
|
89
|
-
maxNotificationsPerPublish: 100,
|
|
90
|
-
publishingEnabled: true,
|
|
91
|
-
priority: 2
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
subscription.on("error", (error) => {
|
|
95
|
-
node.status({ fill: "red", shape: "ring", text: "subscription error" });
|
|
96
|
-
node.error(error);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
node.subscription = subscription;
|
|
100
|
-
|
|
101
|
-
const eventFilter = constructEventFilter([
|
|
102
|
-
"EventId",
|
|
103
|
-
"EventType",
|
|
104
|
-
"SourceName",
|
|
105
|
-
"SourceNode",
|
|
106
|
-
"Message",
|
|
107
|
-
"Severity",
|
|
108
|
-
"ActiveState",
|
|
109
|
-
"AckedState",
|
|
110
|
-
"ConfirmedState",
|
|
111
|
-
"Time"
|
|
112
|
-
]);
|
|
113
|
-
|
|
114
|
-
node.monitoredItems = items.map((item) => {
|
|
115
|
-
const monitoredItem = ClientMonitoredItem.create(
|
|
116
|
-
subscription,
|
|
117
|
-
{
|
|
118
|
-
nodeId: resolveNodeId(item),
|
|
119
|
-
attributeId: AttributeIds.EventNotifier
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
samplingInterval: 0,
|
|
123
|
-
queueSize: 100,
|
|
124
|
-
discardOldest: true,
|
|
125
|
-
filter: eventFilter
|
|
126
|
-
},
|
|
127
|
-
TimestampsToReturn.Both
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
monitoredItem.on("changed", async (eventFields) => {
|
|
131
|
-
const payload = await dataValueToItemResultEvent(item, eventFields, session);
|
|
132
|
-
node.status({
|
|
133
|
-
fill: "blue",
|
|
134
|
-
shape: "dot",
|
|
135
|
-
text: resolveName(item, payload.nodeID) + " changed"
|
|
136
|
-
});
|
|
137
|
-
node.send({
|
|
138
|
-
topic: payload.name,
|
|
139
|
-
payload,
|
|
140
|
-
opcua: {
|
|
141
|
-
mode: "subscription",
|
|
142
|
-
nodeID: payload.nodeID,
|
|
143
|
-
status: payload.status
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
monitoredItem.on("err", (message) => {
|
|
149
|
-
node.status({ fill: "red", shape: "ring", text: statusCodeToString(message) });
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
return monitoredItem;
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
return items;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
async stop(node) {
|
|
159
|
-
const subscription = node.subscription;
|
|
160
|
-
node.monitoredItems = [];
|
|
161
|
-
node.subscription = null;
|
|
162
|
-
|
|
163
|
-
if (subscription) {
|
|
164
|
-
await subscription.terminate();
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
module.exports = {
|
|
170
|
-
OpcUaClientSubscriptionService
|
|
171
|
-
};
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const { DataType, coerceNodeId } = require("node-opcua");
|
|
4
|
-
const {
|
|
5
|
-
buildVariantFromItem,
|
|
6
|
-
dataValueToItemResult,
|
|
7
|
-
normalizeTypeName,
|
|
8
|
-
resolveNodeId,
|
|
9
|
-
statusCodeToString
|
|
10
|
-
} = require("../opcua-client-utils");
|
|
11
|
-
|
|
12
|
-
class OpcUaClientWriteService {
|
|
13
|
-
async execute(node, msg, session, itemsResolver) {
|
|
14
|
-
const items = itemsResolver.ensureWriteItems(node, msg);
|
|
15
|
-
const results = [];
|
|
16
|
-
|
|
17
|
-
for (const item of items) {
|
|
18
|
-
const nodeId = resolveNodeId(item);
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
const explicitType = normalizeTypeName(item.type);
|
|
22
|
-
const builtInType = await session.getBuiltInDataType(coerceNodeId(nodeId));
|
|
23
|
-
const typeName = explicitType || DataType[builtInType];
|
|
24
|
-
const variant = buildVariantFromItem(item, typeName);
|
|
25
|
-
const statusCode = await session.writeSingleNode(nodeId, variant);
|
|
26
|
-
const dataValue = await session.readVariableValue(nodeId);
|
|
27
|
-
const result = dataValueToItemResult(item, dataValue);
|
|
28
|
-
|
|
29
|
-
if (statusCode && statusCode.name && statusCode.name !== "Good") {
|
|
30
|
-
result.status = statusCodeToString(statusCode);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
results.push(result);
|
|
34
|
-
} catch (itemError) {
|
|
35
|
-
results.push({
|
|
36
|
-
name: item.name || nodeId,
|
|
37
|
-
nodeID: nodeId,
|
|
38
|
-
value: item.value,
|
|
39
|
-
type: normalizeTypeName(item.type) || null,
|
|
40
|
-
status: itemError.message,
|
|
41
|
-
sourceTimestamp: null,
|
|
42
|
-
serverTimestamp: null
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return results;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
module.exports = {
|
|
52
|
-
OpcUaClientWriteService
|
|
53
|
-
};
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
<script type="text/javascript">
|
|
2
|
-
(function () {
|
|
3
|
-
function toggleCredentials() {
|
|
4
|
-
var authType = $("#node-config-input-authType").val();
|
|
5
|
-
$(".opcua-client-auth-row").toggle(authType === "username");
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
RED.nodes.registerType("opcua-client-config", {
|
|
9
|
-
category: "config",
|
|
10
|
-
defaults: {
|
|
11
|
-
name: { value: "" },
|
|
12
|
-
endpoint: { value: "opc.tcp://localhost:4840", required: true },
|
|
13
|
-
securityPolicy: { value: "None", required: true },
|
|
14
|
-
securityMode: { value: "None", required: true },
|
|
15
|
-
authType: { value: "anonymous", required: true }
|
|
16
|
-
},
|
|
17
|
-
credentials: {
|
|
18
|
-
username: { type: "text" },
|
|
19
|
-
password: { type: "password" }
|
|
20
|
-
},
|
|
21
|
-
label: function () {
|
|
22
|
-
return this.name || this.endpoint || "opcua-client-config";
|
|
23
|
-
},
|
|
24
|
-
oneditprepare: function () {
|
|
25
|
-
$("#node-config-input-authType").on("change", toggleCredentials);
|
|
26
|
-
toggleCredentials();
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
})();
|
|
30
|
-
</script>
|
|
31
|
-
|
|
32
|
-
<script type="text/html" data-template-name="opcua-client-config">
|
|
33
|
-
<div class="form-row">
|
|
34
|
-
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
35
|
-
<input type="text" id="node-config-input-name" placeholder="OPC UA Client">
|
|
36
|
-
</div>
|
|
37
|
-
<div class="form-row">
|
|
38
|
-
<label for="node-config-input-endpoint"><i class="fa fa-plug"></i> Endpoint</label>
|
|
39
|
-
<input type="text" id="node-config-input-endpoint" placeholder="opc.tcp://localhost:4840">
|
|
40
|
-
</div>
|
|
41
|
-
<div class="form-row">
|
|
42
|
-
<label for="node-config-input-securityPolicy"><i class="fa fa-lock"></i> Security Policy</label>
|
|
43
|
-
<select id="node-config-input-securityPolicy">
|
|
44
|
-
<option value="None">None</option>
|
|
45
|
-
<option value="Basic128Rsa15">Basic128Rsa15</option>
|
|
46
|
-
<option value="Basic256">Basic256</option>
|
|
47
|
-
<option value="Basic256Sha256">Basic256Sha256</option>
|
|
48
|
-
<option value="Aes128_Sha256_RsaOaep">Aes128_Sha256_RsaOaep</option>
|
|
49
|
-
<option value="Aes256_Sha256_RsaPss">Aes256_Sha256_RsaPss</option>
|
|
50
|
-
</select>
|
|
51
|
-
</div>
|
|
52
|
-
<div class="form-row">
|
|
53
|
-
<label for="node-config-input-securityMode"><i class="fa fa-shield"></i> Security Mode</label>
|
|
54
|
-
<select id="node-config-input-securityMode">
|
|
55
|
-
<option value="None">None</option>
|
|
56
|
-
<option value="Sign">Sign</option>
|
|
57
|
-
<option value="SignAndEncrypt">SignAndEncrypt</option>
|
|
58
|
-
</select>
|
|
59
|
-
</div>
|
|
60
|
-
<div class="form-row">
|
|
61
|
-
<label for="node-config-input-authType"><i class="fa fa-user"></i> Auth</label>
|
|
62
|
-
<select id="node-config-input-authType">
|
|
63
|
-
<option value="anonymous">Anonymous</option>
|
|
64
|
-
<option value="username">Username/Password</option>
|
|
65
|
-
</select>
|
|
66
|
-
</div>
|
|
67
|
-
<div class="form-row opcua-client-auth-row">
|
|
68
|
-
<label for="node-config-input-username"><i class="fa fa-user-circle"></i> Username</label>
|
|
69
|
-
<input type="text" id="node-config-input-username">
|
|
70
|
-
</div>
|
|
71
|
-
<div class="form-row opcua-client-auth-row">
|
|
72
|
-
<label for="node-config-input-password"><i class="fa fa-key"></i> Password</label>
|
|
73
|
-
<input type="password" id="node-config-input-password">
|
|
74
|
-
</div>
|
|
75
|
-
</script>
|
|
76
|
-
|
|
77
|
-
<script type="text/html" data-help-name="opcua-client-config">
|
|
78
|
-
<p>Shared OPC UA client connection for read, write and method nodes.</p>
|
|
79
|
-
<p>This is a Node-RED configuration node, using <code>category: "config"</code> and the <code>node-config-input-*</code> field pattern described in the official docs.</p>
|
|
80
|
-
</script>
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const {
|
|
4
|
-
OPCUAClient,
|
|
5
|
-
getMethodArgumentDefinition,
|
|
6
|
-
resolveMethodObjectId,
|
|
7
|
-
resolveSecurityMode,
|
|
8
|
-
resolveSecurityPolicy
|
|
9
|
-
} = require("./opcua-client-utils");
|
|
10
|
-
|
|
11
|
-
const {
|
|
12
|
-
browseNode,
|
|
13
|
-
ROOT_NODE_ID
|
|
14
|
-
} = require("./lib/opcua-client-browser");
|
|
15
|
-
|
|
16
|
-
module.exports = function (RED) {
|
|
17
|
-
function OpcUaClientConfigNode(config) {
|
|
18
|
-
RED.nodes.createNode(this, config);
|
|
19
|
-
const node = this;
|
|
20
|
-
|
|
21
|
-
node.name = (config.name || "").trim();
|
|
22
|
-
node.endpoint = (config.endpoint || "").trim();
|
|
23
|
-
node.securityPolicy = config.securityPolicy || "None";
|
|
24
|
-
node.securityMode = config.securityMode || "None";
|
|
25
|
-
node.authType = config.authType || "anonymous";
|
|
26
|
-
node.client = null;
|
|
27
|
-
node.session = null;
|
|
28
|
-
node.connectPromise = null;
|
|
29
|
-
node.methodObjectIdCache = new Map();
|
|
30
|
-
node.methodDefinitionCache = new Map();
|
|
31
|
-
|
|
32
|
-
node.on("close", function (done) {
|
|
33
|
-
node.closeConnection().then(() => done(), done);
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
OpcUaClientConfigNode.prototype.getSession = async function () {
|
|
38
|
-
if (this.session) {
|
|
39
|
-
return this.session;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (this.connectPromise) {
|
|
43
|
-
return this.connectPromise;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (!this.endpoint) {
|
|
47
|
-
throw new Error("OPC UA endpoint is not configured");
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
this.connectPromise = (async () => {
|
|
51
|
-
const client = OPCUAClient.create({
|
|
52
|
-
endpointMustExist: false,
|
|
53
|
-
keepSessionAlive: true,
|
|
54
|
-
securityMode: resolveSecurityMode(this.securityMode),
|
|
55
|
-
securityPolicy: resolveSecurityPolicy(this.securityPolicy)
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
await client.connect(this.endpoint);
|
|
60
|
-
|
|
61
|
-
const credentials = this.credentials || {};
|
|
62
|
-
const session = this.authType === "username"
|
|
63
|
-
? await client.createSession({
|
|
64
|
-
userName: credentials.username || "",
|
|
65
|
-
password: credentials.password || ""
|
|
66
|
-
})
|
|
67
|
-
: await client.createSession();
|
|
68
|
-
|
|
69
|
-
session.on("session_closed", () => {
|
|
70
|
-
this.session = null;
|
|
71
|
-
this.methodObjectIdCache.clear();
|
|
72
|
-
this.methodDefinitionCache.clear();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
this.client = client;
|
|
76
|
-
this.session = session;
|
|
77
|
-
return session;
|
|
78
|
-
} catch (error) {
|
|
79
|
-
try {
|
|
80
|
-
await client.disconnect();
|
|
81
|
-
} catch (disconnectError) {
|
|
82
|
-
// Ignore secondary disconnect failures after a failed connect/createSession.
|
|
83
|
-
}
|
|
84
|
-
this.client = null;
|
|
85
|
-
this.session = null;
|
|
86
|
-
throw error;
|
|
87
|
-
} finally {
|
|
88
|
-
this.connectPromise = null;
|
|
89
|
-
}
|
|
90
|
-
})();
|
|
91
|
-
|
|
92
|
-
return this.connectPromise;
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
OpcUaClientConfigNode.prototype.resolveMethodObjectId = async function (session, methodNodeId) {
|
|
96
|
-
return resolveMethodObjectId(session, methodNodeId, this.methodObjectIdCache);
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
OpcUaClientConfigNode.prototype.getMethodArgumentDefinition = async function (session, methodNodeId) {
|
|
100
|
-
return getMethodArgumentDefinition(session, methodNodeId, this.methodDefinitionCache);
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
OpcUaClientConfigNode.prototype.closeConnection = async function () {
|
|
104
|
-
const session = this.session;
|
|
105
|
-
const client = this.client;
|
|
106
|
-
|
|
107
|
-
this.session = null;
|
|
108
|
-
this.client = null;
|
|
109
|
-
this.connectPromise = null;
|
|
110
|
-
this.methodObjectIdCache.clear();
|
|
111
|
-
this.methodDefinitionCache.clear();
|
|
112
|
-
|
|
113
|
-
if (session) {
|
|
114
|
-
try {
|
|
115
|
-
await session.close();
|
|
116
|
-
} catch (error) {
|
|
117
|
-
// Ignore close errors during shutdown.
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (client) {
|
|
122
|
-
try {
|
|
123
|
-
await client.disconnect();
|
|
124
|
-
} catch (error) {
|
|
125
|
-
// Ignore disconnect errors during shutdown.
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
RED.nodes.registerType("opcua-client-config", OpcUaClientConfigNode, {
|
|
131
|
-
credentials: {
|
|
132
|
-
username: { type: "text" },
|
|
133
|
-
password: { type: "password" }
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
RED.httpAdmin.get("/opcua-client-config/:id/browse", RED.auth.needsPermission("flows.read"), async function (req, res) {
|
|
138
|
-
try {
|
|
139
|
-
const configNode = RED.nodes.getNode(req.params.id);
|
|
140
|
-
|
|
141
|
-
if (!configNode) {
|
|
142
|
-
res.status(404).json({ error: "OPC UA client configuration not found" });
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const payload = await browseForEditor(configNode, req.query.nodeId);
|
|
147
|
-
res.json(payload);
|
|
148
|
-
} catch (error) {
|
|
149
|
-
res.status(500).json({ error: error.message });
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
async function browseForEditor(configNode, nodeId) {
|
|
155
|
-
const session = await configNode.getSession();
|
|
156
|
-
return browseNode(session, {
|
|
157
|
-
nodeID: nodeId || ROOT_NODE_ID
|
|
158
|
-
});
|
|
159
|
-
}
|