diff --git a/core.js b/core.js index df965f8..105f423 100644 --- a/core.js +++ b/core.js @@ -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, @@ -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 \ No newline at end of file diff --git a/opcua-browse.js b/opcua-browse.js index b3edc93..8d8b761 100644 --- a/opcua-browse.js +++ b/opcua-browse.js @@ -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; @@ -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) { diff --git a/opcua-client.js b/opcua-client.js index 92e8e05..16c3bee 100644 --- a/opcua-client.js +++ b/opcua-client.js @@ -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(); @@ -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 diff --git a/opcua-read.js b/opcua-read.js index c692e6d..0282e74 100644 --- a/opcua-read.js +++ b/opcua-read.js @@ -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; @@ -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 }); diff --git a/opcua-status.js b/opcua-status.js index 6c5fef4..6a030f1 100644 --- a/opcua-status.js +++ b/opcua-status.js @@ -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); } diff --git a/package-lock.json b/package-lock.json index 6ba0c3d..18f7f44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "node-red-opcua-x", - "version": "0.2.0", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "node-red-opcua-x", - "version": "0.2.0", + "version": "0.3.0", "dependencies": { "node-opcua": "^2.127.1" } diff --git a/package.json b/package.json index 9d810c4..7b6afcd 100644 --- a/package.json +++ b/package.json @@ -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",