Skip to content

Commit 082e6e6

Browse files
TeoMatteo Fantin
authored andcommitted
fix multiple connection opc client
1 parent 65ee76d commit 082e6e6

File tree

7 files changed

+135
-89
lines changed

7 files changed

+135
-89
lines changed

core.js

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,22 @@
66
*
77
* @Namespace core
88
*/
9-
var core = core || { opcClient: {} }
9+
var core = core || { opcClients: [] }
1010
core.opcua = core.opcua || require('node-opcua')
11-
core.opcClient = core.opcClient || new core.opcua.OPCUAClient();
12-
core.opcSession = core.opcSession || null
13-
core.opcClientStatus = core.opcClientStatus || "disconnected"
11+
core.opcClients = core.opcClients || {}
1412

15-
core.createOpcUaClient = function(name){
13+
core.createOpcUaClient = function (connectionId, name) {
1614
// caused the connect method to fail after one single unsuccessful retry
1715
const connectionStrategy = {
1816
initialDelay: 1000,
1917
maxDelay: 10000,
20-
maxRetry: 3
18+
maxRetry: 100
2119
};
22-
23-
core.opcClient = core.opcua.OPCUAClient.create({
20+
21+
const existingClient = core.opcClients[connectionId];
22+
if (existingClient) return existingClient;
23+
24+
const newClient = core.opcua.OPCUAClient.create({
2425
applicationName: name,
2526
keepSessionAlive: true,
2627
keepAliveInterval: 5000,
@@ -29,20 +30,93 @@ core.createOpcUaClient = function(name){
2930
securityPolicy: core.opcua.SecurityPolicy.None, //TODO
3031
endpointMustExist: false
3132
});
33+
core.opcClients[connectionId] = newClient;
3234

33-
return core.opcClient;
35+
return newClient;
3436
}
3537

36-
core.connect = async function(host){
37-
await core.opcClient.connect(host);
38-
core.opcClientStatus = "connected";
39-
core.opcSession = await core.opcClient.createSession();
38+
core.connect = async function (connectionId, host) {
39+
const existingClient = core.opcClients[connectionId];
40+
if (!existingClient) return;
41+
42+
existingClient.on("abort", () => updateClientConnectionStatus(connectionId, "disconnected"));
43+
existingClient.on("close", () => updateClientConnectionStatus(connectionId, "disconnected"));
44+
existingClient.on("connection_reestablished", () => updateClientConnectionStatus(connectionId, "connected"));
45+
existingClient.on("connection_lost", () => updateClientConnectionStatus(connectionId, "disconnected"));
46+
existingClient.on("start_reconnection", () => updateClientConnectionStatus(connectionId, "reconnecting"));
47+
existingClient.on("after_reconnection", () => updateClientConnectionStatus(connectionId, "reconnecting"));
48+
49+
try{
50+
await existingClient.connect(host);
51+
52+
const session = await existingClient.createSession();
53+
session.on("session_closed", () => updateClientConnectionStatus(connectionId, "disconnected"));
54+
// session.on("keepalive", () => node.debug(connectionId, "session keepalive"));
55+
// session.on("keepalive_failure", () => node.debug(connectionId, "session keepalive failure"));
56+
existingClient['session'] = session;
57+
}
58+
catch(err){
59+
console.error(err);
60+
updateClientConnectionStatus(connectionId, "disconnected");
61+
return;
62+
}
63+
64+
// if all went well, set status connected!
65+
updateClientConnectionStatus(connectionId, "connected");
4066
}
4167

42-
core.close = async function(){
43-
await core.opcSession.close();
44-
core.opcClientStatus = "disconnected";
45-
core.opcSession = null;
68+
core.close = async function (connectionId) {
69+
let existingClient = core.opcClients[connectionId];
70+
if (!existingClient) return;
71+
72+
let session = existingClient.session;
73+
if (!session) return;
74+
75+
try{
76+
session.removeListener("session_closed", () => updateClientConnectionStatus(connectionId, "disconnected"));
77+
78+
await session.close();
79+
80+
session = null;
81+
existingClient.session = null;
82+
83+
// detach all events before destroy client
84+
existingClient.removeListener("abort", () => updateClientConnectionStatus(connectionId, "disconnected"));
85+
existingClient.removeListener("close", () => updateClientConnectionStatus(connectionId, "disconnected"));
86+
existingClient.removeListener("connection_reestablished", () => updateClientConnectionStatus(connectionId, "connected"));
87+
existingClient.removeListener("connection_lost", () => updateClientConnectionStatus(connectionId, "disconnected"));
88+
existingClient.removeListener("start_reconnection", () => updateClientConnectionStatus(connectionId, "reconnecting"));
89+
existingClient.removeListener("after_reconnection", () => updateClientConnectionStatus(connectionId, "reconnecting"));
90+
91+
if(!existingClient.isReconnecting){
92+
existingClient.disconnect();
93+
}
94+
95+
existingClient = null;
96+
core.opcClients[connectionId] = null;
97+
98+
}catch(err){
99+
console.error(err);
100+
}
101+
}
102+
103+
function updateClientConnectionStatus(connectionId, status) {
104+
const existingClient = core.opcClients[connectionId];
105+
if (!existingClient) return;
106+
107+
switch (status) {
108+
case "connected":
109+
console.debug(existingClient.applicationName + ":client has reconnected");
110+
break;
111+
case "reconnecting":
112+
console.debug(existingClient.applicationName + ":client is trying to reconnect");
113+
break
114+
case "disconnected":
115+
console.debug(existingClient.applicationName + ":client has lost connection");
116+
break;
117+
}
118+
119+
existingClient['clientState'] = status;
46120
}
47121

48122
module.exports = core

opcua-browse.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ module.exports = function (RED) {
44

55
function opcUaBrowseNode(args) {
66
RED.nodes.createNode(this, args);
7+
const opcuaclientnode = RED.nodes.getNode(args.client);
78

89
let node = this;
910

@@ -12,15 +13,20 @@ module.exports = function (RED) {
1213

1314
// Read Input Arg node
1415
node.on('input', function (msg) {
16+
const existingClient = core.opcClients[opcuaclientnode.connectionId];
17+
if(!existingClient){
18+
node.error("OPC UA Client not defined");
19+
return;
20+
}
1521

1622
// Override nodeId from incoming node if not defined on read node
1723
if (!args.nodeId && msg.nodeId) node.nodeId = msg.nodeId;
1824

19-
browseNode();
25+
browseNode(existingClient);
2026
});
2127

22-
async function browseNode() {
23-
const browseResult = await core.opcSession.browse(node.nodeId);
28+
async function browseNode(opcClient) {
29+
const browseResult = await opcClient.session.browse(node.nodeId);
2430

2531
items = [browseResult.references.length];
2632
for (const index in browseResult.references) {

opcua-client.js

Lines changed: 5 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ module.exports = function (RED) {
88

99
let node = this;
1010

11+
node.connectionId = args.id;
1112
node.name = args.name; //unique name identifier
1213
node.host = args.host; //opc.tcp
1314

1415
// Setup client
1516
node.debug('Setup opc client for ' + node.name);
16-
const opcClient = core.createOpcUaClient(node.name);
17+
const opcClient = core.createOpcUaClient(node.connectionId, node.name);
1718

1819
// Connect client
1920
connect();
@@ -24,57 +25,16 @@ module.exports = function (RED) {
2425
//#region Methods
2526

2627
async function connect() {
27-
try {
28-
29-
opcClient.on("abort", () => sendClientConnectionStatus("disconnected"));
30-
opcClient.on("close", () => sendClientConnectionStatus("disconnected"));
31-
opcClient.on("connection_reestablished", () => sendClientConnectionStatus("connected"));
32-
opcClient.on("connection_lost", () => sendClientConnectionStatus("disconnected"));
33-
opcClient.on("start_reconnection", () => sendClientConnectionStatus("reconnecting"));
34-
opcClient.on("after_reconnection", () => sendClientConnectionStatus("reconnecting"));
35-
36-
await core.connect(node.host);
37-
38-
core.opcSession.on("session_closed", () => sendClientConnectionStatus("disconnected"));
39-
// core.opcSession.on("keepalive", () => node.debug("session keepalive"));
40-
// core.opcSession.on("keepalive_failure", () => node.debug("session keepalive failure"));
41-
42-
} catch (err) {
43-
node.error(err);
44-
}
45-
}
46-
47-
function sendClientConnectionStatus(status) {
48-
switch(status){
49-
case "connected":
50-
node.debug("client has reconnected");
51-
break;
52-
case "reconnecting":
53-
node.debug("client is trying to reconnect");
54-
break
55-
case "disconnected":
56-
node.debug("client has lost connection");
57-
break;
58-
}
59-
60-
core.opcClientStatus = status;
28+
await core.connect(node.connectionId, node.host);
6129
}
6230

6331
async function onNodeClosed(done){
64-
try{
65-
await core.close();
66-
}catch(err){
67-
node.error(err);
68-
}
32+
await core.close(node.connectionId);
6933
done();
7034
}
7135

7236
function onNodeError(){
73-
try{
74-
core.close();
75-
}catch(err){
76-
node.error(err);
77-
}
37+
core.close(node.connectionId);
7838
}
7939

8040
//#endregion

opcua-read.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ module.exports = function (RED) {
66
function opcUaReadNode(args) {
77

88
RED.nodes.createNode(this, args);
9+
const opcuaclientnode = RED.nodes.getNode(args.client);
910

1011
var node = this;
1112

@@ -14,24 +15,24 @@ module.exports = function (RED) {
1415

1516
// Read Input Arg node
1617
node.on('input', function (msg) {
17-
18-
if(!core.opcSession){
19-
node.error("Session not defined");
18+
const existingClient = core.opcClients[opcuaclientnode.connectionId];
19+
if(!existingClient){
20+
node.error("OPC UA Client not defined");
2021
return;
2122
}
2223

2324
// Override nodeId from incoming node if not defined on read node
2425
if (!args.nodeId && msg.nodeId) node.nodeId = msg.nodeId;
2526

26-
readNode();
27+
readNode(existingClient);
2728
});
2829

29-
async function readNode() {
30+
async function readNode(opcClient) {
3031
const nodeToRead = {
3132
nodeId: node.nodeId,
3233
attributeId: opcua.AttributeIds.Value
3334
};
34-
const dataValue = await core.opcSession.read(nodeToRead);
35+
const dataValue = await opcClient.session.read(nodeToRead);
3536
const value = dataValue.value;
3637
const statusCode = dataValue.statusCode;
3738
node.send({ payload: value });

opcua-status.js

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,35 @@ module.exports = function (RED) {
55
function opcUaStatusNode(config) {
66

77
RED.nodes.createNode(this, config);
8+
const opcuaclientnode = RED.nodes.getNode(config.client);
89

910
let node = this;
1011
let state = false;
1112

12-
setInterval(checkServerConnection, 5000);
13+
setInterval(checkServerConnection, 1000);
1314

1415
function checkServerConnection() {
15-
if (state === core.opcClientStatus) return;
16-
state = core.opcClientStatus;
17-
18-
switch(state){
19-
case "connected":
20-
node.status({ fill: "green", shape: "dot", text: "connected" });
21-
break;
22-
case "reconnecting":
23-
node.status({ fill: "yellow", shape: "ring", text: "reconnecting" });
24-
break;
25-
case "disconnected":
26-
default:
27-
node.status({ fill: "red", shape: "ring", text: "disconnected" });
16+
const existingClient = core.opcClients[opcuaclientnode.connectionId];
17+
if (existingClient) {
18+
if (state === existingClient.clientState) return;
19+
state = existingClient.clientState;
20+
21+
switch (state) {
22+
case "connected":
23+
node.status({ fill: "green", shape: "dot", text: "connected" });
24+
break;
25+
case "reconnecting":
26+
node.status({ fill: "yellow", shape: "ring", text: "reconnecting" });
27+
break;
28+
case "disconnected":
29+
default:
30+
node.status({ fill: "red", shape: "ring", text: "disconnected" });
31+
}
32+
} else {
33+
state = "disconnected";
2834
}
2935

3036
var msg = { payload: state };
31-
3237
node.send(msg);
3338
}
3439

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "node-red-opcua-x",
3-
"version": "0.2.0",
3+
"version": "0.3.0",
44
"node-red": {
55
"nodes": {
66
"opcua-client": "opcua-client.js",

0 commit comments

Comments
 (0)