Skip to content

Commit

Permalink
fix multiple connection opc client
Browse files Browse the repository at this point in the history
  • Loading branch information
matteo-fantin committed Jul 5, 2024
1 parent 65ee76d commit d681a94
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 89 deletions.
108 changes: 91 additions & 17 deletions core.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,22 @@
*
* @Namespace core
*/
var core = core || { opcClient: {} }
var core = core || { opcClients: [] }
core.opcua = core.opcua || require('node-opcua')
core.opcClient = core.opcClient || new core.opcua.OPCUAClient();
core.opcSession = core.opcSession || null
core.opcClientStatus = core.opcClientStatus || "disconnected"
core.opcClients = core.opcClients || {}

core.createOpcUaClient = function(name){
core.createOpcUaClient = function (connectionId, name) {
// caused the connect method to fail after one single unsuccessful retry
const connectionStrategy = {
initialDelay: 1000,
maxDelay: 10000,
maxRetry: 3
maxRetry: 100
};

core.opcClient = core.opcua.OPCUAClient.create({

const existingClient = core.opcClients[connectionId];
if (existingClient) return existingClient;

const newClient = core.opcua.OPCUAClient.create({
applicationName: name,
keepSessionAlive: true,
keepAliveInterval: 5000,
Expand All @@ -29,20 +30,93 @@ core.createOpcUaClient = function(name){
securityPolicy: core.opcua.SecurityPolicy.None, //TODO
endpointMustExist: false
});
core.opcClients[connectionId] = newClient;

return core.opcClient;
return newClient;
}

core.connect = async function(host){
await core.opcClient.connect(host);
core.opcClientStatus = "connected";
core.opcSession = await core.opcClient.createSession();
core.connect = async function (connectionId, host) {
const existingClient = core.opcClients[connectionId];
if (!existingClient) return;

existingClient.on("abort", () => updateClientConnectionStatus(connectionId, "disconnected"));
existingClient.on("close", () => updateClientConnectionStatus(connectionId, "disconnected"));
existingClient.on("connection_reestablished", () => updateClientConnectionStatus(connectionId, "connected"));
existingClient.on("connection_lost", () => updateClientConnectionStatus(connectionId, "disconnected"));
existingClient.on("start_reconnection", () => updateClientConnectionStatus(connectionId, "reconnecting"));
existingClient.on("after_reconnection", () => updateClientConnectionStatus(connectionId, "reconnecting"));

try{
await existingClient.connect(host);

const session = await existingClient.createSession();
session.on("session_closed", () => updateClientConnectionStatus(connectionId, "disconnected"));
// session.on("keepalive", () => node.debug(connectionId, "session keepalive"));
// session.on("keepalive_failure", () => node.debug(connectionId, "session keepalive failure"));
existingClient['session'] = session;
}
catch(err){
console.error(err);
updateClientConnectionStatus(connectionId, "disconnected");
return;
}

// if all went well, set status connected!
updateClientConnectionStatus(connectionId, "connected");
}

core.close = async function(){
await core.opcSession.close();
core.opcClientStatus = "disconnected";
core.opcSession = null;
core.close = async function (connectionId) {
let existingClient = core.opcClients[connectionId];
if (!existingClient) return;

let session = existingClient.session;
if (!session) return;

try{
session.removeListener("session_closed", () => updateClientConnectionStatus(connectionId, "disconnected"));

await session.close();

session = null;
existingClient.session = null;

// detach all events before destroy client
existingClient.removeListener("abort", () => updateClientConnectionStatus(connectionId, "disconnected"));
existingClient.removeListener("close", () => updateClientConnectionStatus(connectionId, "disconnected"));
existingClient.removeListener("connection_reestablished", () => updateClientConnectionStatus(connectionId, "connected"));
existingClient.removeListener("connection_lost", () => updateClientConnectionStatus(connectionId, "disconnected"));
existingClient.removeListener("start_reconnection", () => updateClientConnectionStatus(connectionId, "reconnecting"));
existingClient.removeListener("after_reconnection", () => updateClientConnectionStatus(connectionId, "reconnecting"));

if(!existingClient.isReconnecting){
existingClient.disconnect();
}

existingClient = null;
core.opcClients[connectionId] = null;

}catch(err){
console.error(err);
}
}

function updateClientConnectionStatus(connectionId, status) {
const existingClient = core.opcClients[connectionId];
if (!existingClient) return;

switch (status) {
case "connected":
console.debug(existingClient.applicationName + ":client has reconnected");
break;
case "reconnecting":
console.debug(existingClient.applicationName + ":client is trying to reconnect");
break
case "disconnected":
console.debug(existingClient.applicationName + ":client has lost connection");
break;
}

existingClient['clientState'] = status;
}

module.exports = core
12 changes: 9 additions & 3 deletions opcua-browse.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module.exports = function (RED) {

function opcUaBrowseNode(args) {
RED.nodes.createNode(this, args);
const opcuaclientnode = RED.nodes.getNode(args.client);

let node = this;

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

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

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

browseNode();
browseNode(existingClient);
});

async function browseNode() {
const browseResult = await core.opcSession.browse(node.nodeId);
async function browseNode(opcClient) {
const browseResult = await opcClient.session.browse(node.nodeId);

items = [browseResult.references.length];
for (const index in browseResult.references) {
Expand Down
50 changes: 5 additions & 45 deletions opcua-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ module.exports = function (RED) {

let node = this;

node.connectionId = args.id;
node.name = args.name; //unique name identifier
node.host = args.host; //opc.tcp

// Setup client
node.debug('Setup opc client for ' + node.name);
const opcClient = core.createOpcUaClient(node.name);
const opcClient = core.createOpcUaClient(node.connectionId, node.name);

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

async function connect() {
try {

opcClient.on("abort", () => sendClientConnectionStatus("disconnected"));
opcClient.on("close", () => sendClientConnectionStatus("disconnected"));
opcClient.on("connection_reestablished", () => sendClientConnectionStatus("connected"));
opcClient.on("connection_lost", () => sendClientConnectionStatus("disconnected"));
opcClient.on("start_reconnection", () => sendClientConnectionStatus("reconnecting"));
opcClient.on("after_reconnection", () => sendClientConnectionStatus("reconnecting"));

await core.connect(node.host);

core.opcSession.on("session_closed", () => sendClientConnectionStatus("disconnected"));
// core.opcSession.on("keepalive", () => node.debug("session keepalive"));
// core.opcSession.on("keepalive_failure", () => node.debug("session keepalive failure"));

} catch (err) {
node.error(err);
}
}

function sendClientConnectionStatus(status) {
switch(status){
case "connected":
node.debug("client has reconnected");
break;
case "reconnecting":
node.debug("client is trying to reconnect");
break
case "disconnected":
node.debug("client has lost connection");
break;
}

core.opcClientStatus = status;
await core.connect(node.connectionId, node.host);
}

async function onNodeClosed(done){
try{
await core.close();
}catch(err){
node.error(err);
}
await core.close(node.connectionId);
done();
}

function onNodeError(){
try{
core.close();
}catch(err){
node.error(err);
}
core.close(node.connectionId);
}

//#endregion
Expand Down
13 changes: 7 additions & 6 deletions opcua-read.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module.exports = function (RED) {
function opcUaReadNode(args) {

RED.nodes.createNode(this, args);
const opcuaclientnode = RED.nodes.getNode(args.client);

var node = this;

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

// Read Input Arg node
node.on('input', function (msg) {

if(!core.opcSession){
node.error("Session not defined");
const existingClient = core.opcClients[opcuaclientnode.connectionId];
if(!existingClient){
node.error("OPC UA Client not defined");
return;
}

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

readNode();
readNode(existingClient);
});

async function readNode() {
async function readNode(opcClient) {
const nodeToRead = {
nodeId: node.nodeId,
attributeId: opcua.AttributeIds.Value
};
const dataValue = await core.opcSession.read(nodeToRead);
const dataValue = await opcClient.session.read(nodeToRead);
const value = dataValue.value;
const statusCode = dataValue.statusCode;
node.send({ payload: value });
Expand Down
35 changes: 20 additions & 15 deletions opcua-status.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,35 @@ module.exports = function (RED) {
function opcUaStatusNode(config) {

RED.nodes.createNode(this, config);
const opcuaclientnode = RED.nodes.getNode(config.client);

let node = this;
let state = false;

setInterval(checkServerConnection, 5000);
setInterval(checkServerConnection, 1000);

function checkServerConnection() {
if (state === core.opcClientStatus) return;
state = core.opcClientStatus;

switch(state){
case "connected":
node.status({ fill: "green", shape: "dot", text: "connected" });
break;
case "reconnecting":
node.status({ fill: "yellow", shape: "ring", text: "reconnecting" });
break;
case "disconnected":
default:
node.status({ fill: "red", shape: "ring", text: "disconnected" });
const existingClient = core.opcClients[opcuaclientnode.connectionId];
if (existingClient) {
if (state === existingClient.clientState) return;
state = existingClient.clientState;

switch (state) {
case "connected":
node.status({ fill: "green", shape: "dot", text: "connected" });
break;
case "reconnecting":
node.status({ fill: "yellow", shape: "ring", text: "reconnecting" });
break;
case "disconnected":
default:
node.status({ fill: "red", shape: "ring", text: "disconnected" });
}
} else {
state = "disconnected";
}

var msg = { payload: state };

node.send(msg);
}

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "node-red-opcua-x",
"version": "0.2.0",
"version": "0.3.0",
"node-red": {
"nodes": {
"opcua-client": "opcua-client.js",
Expand Down

0 comments on commit d681a94

Please sign in to comment.