UNPKG

node-red-contrib-opcua

Version:

A Node-RED node to communicate via OPC UA based on node-opcua library.

992 lines (922 loc) 100 kB
/** Copyright 2015 Valmet Automation Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. **/ module.exports = function (RED) { "use strict"; var opcua = require('node-opcua'); const ObjectIds = require("node-opcua-constants"); var fileTransfer = require("node-opcua-file-transfer"); const { NodeCrawler } = require("node-opcua-client-crawler"); var path = require('path'); var os = require("os"); var fs = require("fs"); var chalk = require("chalk"); var opcuaBasics = require('./opcua-basics'); const {parse, stringify} = require('flatted'); const { createServerCertificateManager, createUserCertificateManager } = require("./utils"); function OpcUaServerNode(n) { RED.nodes.createNode(this, n); this.name = n.name; this.port = n.port; this.endpoint = n.endpoint; this.users = n.users; this.nodesetDir = n.nodesetDir; this.autoAcceptUnknownCertificate = n.autoAcceptUnknownCertificate; this.allowAnonymous = n.allowAnonymous; this.endpointNone = n.endpointNone; this.endpointSign = n.endpointSign; this.endpointSignEncrypt = n.endpointSignEncrypt; this.endpointBasic128Rsa15 = n.endpointBasic128Rsa15; this.endpointBasic256 = n.endpointBasic256; this.endpointBasic256Sha256 = n.endpointBasic256Sha256; // Operating limits: this.maxNodesPerBrowse = n.maxNodesPerBrowse; this.maxNodesPerHistoryReadData = n.maxNodesPerHistoryReadData; this.maxNodesPerHistoryReadEvents = n.maxNodesPerHistoryReadEvents; this.maxNodesPerHistoryUpdateData = n.maxNodesPerHistoryUpdateData; this.maxNodesPerRead = n.maxNodesPerRead; this.maxNodesPerWrite = n.maxNodesPerWrite; this.maxNodesPerMethodCall = n.maxNodesPerMethodCall; this.maxNodesPerRegisterNodes = n.maxNodesPerRegisterNodes; this.maxNodesPerNodeManagement = n.maxNodesPerNodeManagement; this.maxMonitoredItemsPerCall = n.maxMonitoredItemsPerCall; this.maxNodesPerHistoryUpdateEvents = n.maxNodesPerHistoryUpdateEvents; this.maxNodesPerTranslateBrowsePathsToNodeIds = n.maxNodesPerTranslateBrowsePathsToNodeIds; this.registerToDiscovery = n.registerToDiscovery; this.constructDefaultAddressSpace = n.constructDefaultAddressSpace; this.maxSessions = Math.max(10,n.maxSessions); // Always have minimum of 10 sessions. var maxConnectionsPerEndpoint = 20; if (n.maxConnectionsPerEndpoint > 20) { maxConnectionsPerEndpoint = n.maxConnectionsPerEndpoint; } var maxMessageSize = 4096; if (n.maxMessageSize > 0) { maxMessageSize = n.maxMessageSize; } var maxBufferSize = 4096; if (n.maxBufferSize > 0) { maxBufferSize = n.maxBufferSize; } var node = this; var variables = { Counter: 0 }; var variablesTs = { Counter: 0 }; var variablesStatus = { Counter: 0 }; var equipmentCounter = 0; var physicalAssetCounter = 0; var equipment; var physicalAssets; var vendorName; var myVar2 = new opcua.Variant({dataType: opcua.DataType.Double, value: 0.0}); var freeMem = new opcua.Variant({dataType: opcua.DataType.Double, value: 0.0}); var equipmentNotFound = true; var initialized = false; var folder = null; let userManager; // users with username, password and role let users = [{ username: "", password: "", roles: "" }]; // Empty as default let savedAddressSpace = ""; if (node.users && node.users.length > 0) { verbose_log(chalk.yellow("Trying to load default users from file: ") + chalk.cyan(node.users) + chalk.yellow(" folder: ") + chalk.cyan(__dirname)); if (fs.existsSync(node.users)) { users = JSON.parse(fs.readFileSync(node.users)); verbose_log(chalk.green("Loaded users: ") + chalk.cyan(JSON.stringify(users))); setUsers(users); } else { // Current working directory let fileName = path.join(process.cwd(), node.users); verbose_log(chalk.yellow("Trying to load default users from file: ") + chalk.cyan(node.users) + chalk.yellow(" folder: ") + chalk.cyan(fileName)); if (fs.existsSync(fileName)) { users = JSON.parse(fs.readFileSync(fileName)); verbose_log(chalk.green("Loaded users: ") + chalk.cyan(JSON.stringify(users))); setUsers(users); } else { let fileName = path.join(process.cwd(), ".node-red", node.users); verbose_log(chalk.yellow("Trying to load default users from file: ") + chalk.cyan(node.users) + chalk.yellow(" folder: ") + chalk.cyan(fileName)); if (fs.existsSync(fileName)) { users = JSON.parse(fs.readFileSync(fileName)); verbose_log(chalk.green("Loaded users: ") + chalk.cyan(JSON.stringify(users))); setUsers(users); } else { verbose_log(chalk.red("File: " + node.users + " not found! You can inject users to server or add file to folder: " + fileName)); node.error("File: " + node.users + " not found! You can inject users to server or add file to folder: " + fileName); } } } } // Server endpoints active configuration var policies = []; var modes = []; // Security modes None | Sign | SignAndEncrypt if (this.endpointNone === true) { policies.push(opcua.SecurityPolicy.None); modes.push(opcua.MessageSecurityMode.None); } if (this.endpointSign === true) { modes.push(opcua.MessageSecurityMode.Sign); } if (this.endpointSignEncrypt === true) { modes.push(opcua.MessageSecurityMode.SignAndEncrypt); } // Security policies if (this.endpointBasic128Rsa15 === true) { policies.push(opcua.SecurityPolicy.Basic128Rsa15); } if (this.endpointBasic256 === true) { policies.push(opcua.SecurityPolicy.Basic256); } if (this.endpointBasic256Sha256 === true) { policies.push(opcua.SecurityPolicy.Basic256Sha256); } // This should be possible to inject for server function setUsers() { // User manager userManager = { isValidUser: (username, password) => { const uIndex = users.findIndex(function(u) { return u.username === username; }); if (uIndex < 0) { // console.log(chalk.red("No such user:" + username)); return false; } if (users[uIndex].password !== password) { // console.log(chalk.red("Wrong password for username: " + username + " tried with wrong password:" + password)); return false; } // console.log(chalk.green("Login successful for username: " + username)); return true; }, getUserRoles: (username) => { if (username === "Anonymous" || username === "anonymous") { return opcua.makeRoles(opcua.WellKnownRoles.Anonymous); } const uIndex = users.findIndex(function(x) { return x.username === username; }); if (uIndex < 0) { // Check this TODO return opcua.makeRoles("AuthenticatedUser"); // opcua.WellKnownRoles.Anonymous; // by default were guest! ( i.e anonymous), read-only access } let userRoles; if (users[uIndex].hasOwnProperty("roles")) { userRoles = users[uIndex].roles; // user can have multiple roles Observer;Engineer } else { console.error("Your users.json is missing roles field for user role! Using Anonymous as default role."); return opcua.makeRoles(opcua.WellKnownRoles.Anonymous); // By default use Anonymous } return opcua.makeRoles(userRoles); } }; } function node_error(err) { // console.error(chalk.red("[Error] Server node error on: " + node.name + " error: " + JSON.stringify(err))); node.error("Server node error on: " + node.name + " error: " + JSON.stringify(err)); } function verbose_warn(logMessage) { //if (RED.settings.verbose) { // console.warn(chalk.yellow("[Warning] "+ (node.name) ? node.name + ': ' + logMessage : 'OpcUaServerNode: ' + logMessage)); node.warn((node.name) ? node.name + ': ' + logMessage : 'OpcUaServerNode: ' + logMessage); //} } function verbose_log(logMessage) { //if (RED.settings.verbose) { // console.log(chalk.cyan(logMessage)); node.debug(logMessage); //} } // Method input / output argument types from string to opcua DataType function getUaDatatype(methodArgType) { if (methodArgType === "String") { return opcua.DataType.String; } if (methodArgType === "Byte") { return opcua.DataType.Byte; } if (methodArgType === "SByte") { return opcua.DataType.SByte; } if (methodArgType === "UInt16") { return opcua.DataType.UInt32; } if (methodArgType === "UInt32") { return opcua.DataType.UInt32; } if (methodArgType === "UInt64") { return opcua.DataType.UInt64; } if (methodArgType === "Int16") { return opcua.DataType.Int32; } if (methodArgType === "Int32") { return opcua.DataType.Int32; } if (methodArgType === "Double") { return opcua.DataType.Double; } if (methodArgType === "Float") { return opcua.DataType.Float; } node.error("Cannot convert given argument: " + methodArgType + " to OPC UA DataType!"); } node.status({ fill: "red", shape: "ring", text: "Not running" }); var xmlFiles = [path.join(__dirname, 'public/vendor/opc-foundation/xml/Opc.Ua.NodeSet2.xml'), // Standard & basic types path.join(__dirname, 'public/vendor/opc-foundation/xml/Opc.Ua.Di.NodeSet2.xml'), // Support for DI Device Information model path.join(__dirname, 'public/vendor/opc-foundation/xml/Opc.Ua.AutoID.NodeSet2.xml'), // Support for RFID Readers path.join(__dirname, 'public/vendor/opc-foundation/xml/Opc.ISA95.NodeSet2.xml') // ISA95 ]; if (savedAddressSpace && savedAddressSpace.length>0) { xmlFiles.push(savedAddressSpace); } // Add custom nodesets (xml-files) for server if (node.nodesetDir && fs.existsSync(node.nodesetDir)) { fs.readdirSync(node.nodesetDir).forEach(fileName => { if (path.extname(fileName).toLowerCase() === '.xml') { xmlFiles.push(path.join(node.nodesetDir, fileName)); }; }); } verbose_log(chalk.yellow("NodeSet: ") + chalk.cyan(xmlFiles.toString())); async function initNewServer() { initialized = false; verbose_log(chalk.yellow("Create Server from XML...")); // DO NOT USE "%FQDN%" anymore, hostname is OK const applicationUri = opcua.makeApplicationUrn(os.hostname(), "node-red-contrib-opcua-server"); verbose_log(chalk.yellow("ApplicationUrn: ") + chalk.cyan(applicationUri)); verbose_log(chalk.yellow("Server certificate manager")); const serverCertificateManager = createServerCertificateManager(); verbose_log(chalk.yellow("User Certificate manager")); const userCertificateManager = createUserCertificateManager(); verbose_log(chalk.yellow("Initializing certificate managers")); await serverCertificateManager.initialize(); await userCertificateManager.initialize(); verbose_log(chalk.green("Certificate managers initialized")); var registerMethod = null; if (node.registerToDiscovery === true) { registerMethod = opcua.RegisterServerMethod.LDS; } node.server_options = { serverCertificateManager, userCertificateManager, securityPolicies: policies, securityModes: modes, allowAnonymous: n.allowAnonymous, port: parseInt(n.port), resourcePath: "/" + node.endpoint, // Option was missing / can be // maxAllowedSessionNumber: 1000, maxConnectionsPerEndpoint: maxConnectionsPerEndpoint, maxMessageSize: maxMessageSize, maxBufferSize: maxBufferSize, nodeset_filename: xmlFiles, serverInfo: { applicationUri, productUri: "Node-RED NodeOPCUA-Server", // applicationName: { text: "Mini NodeOPCUA Server", locale: "en" }, // Set later gatewayServerUri: null, discoveryProfileUri: null, discoveryUrls: [] }, buildInfo: { buildNumber: "", buildDate: "" }, serverCapabilities: { maxBrowseContinuationPoints: 10, maxHistoryContinuationPoints: 10, maxSessions: node.maxSessions, // maxInactiveLockTime, // Get these from the node parameters operationLimits: { maxNodesPerBrowse: node.maxNodesPerBrowse, maxNodesPerHistoryReadData: node.maxNodesPerHistoryReadData, maxNodesPerHistoryReadEvents: node.maxNodesPerHistoryReadEvents, maxNodesPerHistoryUpdateData: node.maxNodesPerHistoryUpdateData, maxNodesPerRead: node.maxNodesPerRead, maxNodesPerWrite: node.maxNodesPerWrite, maxNodesPerMethodCall: node.maxNodesPerMethodCall, maxNodesPerRegisterNodes: node.maxNodesPerRegisterNodes, maxNodesPerNodeManagement: node.maxNodesPerNodeManagement, maxMonitoredItemsPerCall: node.maxMonitoredItemsPerCall, maxNodesPerHistoryUpdateEvents: node.maxNodesPerHistoryUpdateEvents, maxNodesPerTranslateBrowsePathsToNodeIds: node.maxNodesPerTranslateBrowsePathsToNodeIds } }, userManager, // users with username, password & role, see file users.json isAuditing: false, registerServerMethod: registerMethod }; node.server_options.serverInfo = { applicationName: { text: "Node-RED OPCUA" } }; node.server_options.buildInfo = { buildNumber: "0.2.340", buildDate: "2025-06-19T07:37:00" }; var hostname = os.hostname(); var discovery_server_endpointUrl = "opc.tcp://" + hostname + ":4840"; // /UADiscovery"; // Do not use resource path if (node.registerToDiscovery === true) { verbose_log("Registering server to :" + discovery_server_endpointUrl); } } function construct_my_address_space(addressSpace) { verbose_log(chalk.yellow("Server: add VendorName ...")); vendorName = addressSpace.getOwnNamespace().addObject({ organizedBy: addressSpace.rootFolder.objects, nodeId: "ns=1;s=VendorName", browseName: "VendorName" }); equipment = addressSpace.getOwnNamespace().addObject({ organizedBy: vendorName, nodeId: "ns=1;s=Equipment", browseName: "Equipment" }); physicalAssets = addressSpace.getOwnNamespace().addObject({ organizedBy: vendorName, nodeId: "ns=1;s=PhysicalAssets", browseName: "Physical Assets" }); verbose_log(chalk.yellow('Server: add MyVariable2 ...')); var variable2 = 10.0; try { myVar2 = new opcua.Variant({dataType: opcua.DataType.Double, value: variable2}); addressSpace.getOwnNamespace().addVariable({ componentOf: vendorName, nodeId: "ns=1;s=MyVariable2", browseName: "MyVariable2", dataType: "Double", minimumSamplingInterval: 500, value: new opcua.Variant({ dataType: opcua.DataType.Double, value: variable2 }) /* { get: () => { // return myVar2; return new opcua.Variant({ dataType: opcua.DataType.Double, value: variable2 }); }, set: function (variant) { myVar2.value = parseFloat(variant.value); return opcua.StatusCodes.Good; } } */ }); } catch(error) { node_error("Cannot add variable; MyVariable2, error: " + error); } verbose_log(chalk.yellow('Server: add FreeMemory ...')); try { freeMem = new opcua.Variant({ dataType: opcua.DataType.Double, value: available_memory() }); node.freeMem = addressSpace.getOwnNamespace().addVariable({ componentOf: vendorName, nodeId: "ns=1;s=FreeMemory", browseName: "FreeMemory", dataType: "Double", minimumSamplingInterval: 500, value: new opcua.Variant({ dataType: opcua.DataType.Double, value: available_memory() }) /* { get: function () { freeMem.value = available_memory(); // return freeMem; return new opcua.Variant({ dataType: opcua.DataType.Double, value: available_memory() }); } } */ }); } catch (error) { node_error("Cannot add variable; freeMem, error: " + error) } verbose_log(chalk.yellow('Server: add Counter ...')); try { node.vendorName = addressSpace.getOwnNamespace().addVariable({ componentOf: vendorName, nodeId: "ns=1;s=Counter", browseName: "Variables Counter", displayName: "Variables Counter", dataType: "UInt16", minimumSamplingInterval: 500, value: new opcua.Variant({ dataType: opcua.DataType.UInt16, value: Object.keys(variables).length // Counter will show amount of created variables }) /* { get: function () { return new opcua.Variant({ dataType: opcua.DataType.UInt16, value: Object.keys(variables).length // Counter will show amount of created variables }); } } */ }); } catch (error) { node_error("Cannot add variable; Counter, error: " + error); } var method = addressSpace.getOwnNamespace().addMethod( vendorName, { browseName: "Bark", inputArguments: [{ name: "nbBarks", description: { text: "specifies the number of time I should bark" }, dataType: opcua.DataType.UInt32 }, { name: "volume", description: { text: "specifies the sound volume [0 = quiet ,100 = loud]" }, dataType: opcua.DataType.UInt32 }], outputArguments: [{ name: "Barks", description: { text: "the generated barks" }, dataType: opcua.DataType.String, valueRank: 1 }] }); method.bindMethod(function (inputArguments, context, callback) { var nbBarks = inputArguments[0].value; var volume = inputArguments[1].value; verbose_log("Hello World ! I will bark ", nbBarks, " times"); verbose_log("the requested volume is ", volume, ""); var sound_volume = new Array(volume).join("!"); var barks = []; for (var i = 0; i < nbBarks; i++) { barks.push("Whaff" + sound_volume); } var callMethodResult = { statusCode: opcua.StatusCodes.Good, outputArguments: [{ dataType: opcua.DataType.String, arrayType: opcua.VariantArrayType.Array, value: barks }] }; callback(null, callMethodResult); }); } function available_memory() { return os.freemem() / os.totalmem() * 100.0; } (async () => { try { await initNewServer(); // Read & set parameters node.server = new opcua.OPCUAServer(node.server_options); await node.server.initialize(); if (node.constructDefaultAddressSpace === true) { construct_my_address_space(node.server.engine.addressSpace); } await node.server.start(); verbose_log(chalk.yellow("Using server certificate ") + chalk.cyan(node.server.certificateFile)); verbose_log(chalk.yellow("Using PKI folder ") + chalk.cyan(node.server.serverCertificateManager.rootDir)); verbose_log(chalk.yellow("Using UserPKI folder ") + chalk.cyan(node.server.userCertificateManager.rootDir)); verbose_log(chalk.yellow("Trusted certificate folder ") + chalk.cyan(node.server.serverCertificateManager.trustedFolder)); verbose_log(chalk.yellow("Rejected certificate folder ") + chalk.cyan(node.server.serverCertificateManager.rejectedFolder)); // Needed for Alarms and Conditions node.server.engine.addressSpace.installAlarmsAndConditionsService(); opcua.addAggregateSupport(node.server.engine.addressSpace); // Client connects with userName node.server.on("session_activated", (session) => { if (session.userIdentityToken && session.userIdentityToken.userName) { var msg = {}; msg.topic="Username"; msg.payload = session.sessionName.toString(); // session.clientDescription.applicationName.toString(); node.send(msg); } }); // Client connected node.server.on("create_session", function(session) { var msg = {}; msg.topic="Client-connected"; msg.payload = session.sessionName.toString(); // session.clientDescription.applicationName.toString(); node.send(msg); }); // Client disconnected node.server.on("session_closed", function(session, reason) { node.debug(chalk.yellow("Reason: ") + chalk.cyan(reason)); var msg = {}; msg.topic="Client-disconnected"; msg.payload = session.sessionName.toString(); // session.clientDescription.applicationName.toString() + " " + session.sessionName ? session.sessionName.toString() : "<null>"; node.send(msg); }); node.status({ fill: "green", shape: "dot", text: "running" }); initialized = true; } catch (err) { var msg = {}; msg.error = {}; msg.error.message = "Disconnect error: " + err; msg.error.source = this; node.error("Disconnect error: ", msg); } })(); //###################################################################################### node.on("input", function (msg) { verbose_log(JSON.stringify(msg)); if (!node.server || !initialized) { node_error("Server is not running"); return false; } var payload = msg.payload; // modify 5/03/2022 if (contains_necessaryProperties(msg)) { read_message(payload); }else { node.warn('warning: properties like messageType, namespace, variableName or VariableValue is missing.'); } if (contains_opcua_command(payload)) { msg.payload = execute_opcua_command(msg); } if (equipmentNotFound) { var addressSpace = node.server.engine.addressSpace; // node.addressSpace; if (addressSpace === undefined || addressSpace === null) { node_error("addressSpace undefined"); return false; } if (node.constructDefaultAddressSpace === true) { var rootFolder = addressSpace.findNode("ns=1;s=VendorName"); if (!rootFolder) { node_error("VerdorName not found!"); return false; } var references = rootFolder.findReferences("Organizes", true); if (findReference(references, equipment.nodeId)) { verbose_log("Equipment Reference found in VendorName"); equipmentNotFound = false; } else { verbose_warn("Equipment Reference not found in VendorName"); } } } node.send(msg); }); function findReference(references, nodeId) { return references.filter(function (r) { return r.nodeId.toString() === nodeId.toString(); }); } // check json object - modify 5/03/2022 function contains_messageType(payload) { return payload.hasOwnProperty('messageType'); } function contains_namespace(payload) { if (!payload.hasOwnProperty('namespace')) node.warn("Mandatory parameter 'namespace' is missing") return payload.hasOwnProperty('namespace'); } function contains_variableName(payload) { if (!payload.hasOwnProperty('variableName')) node.warn("Mandatory parameter 'variableName' missing"); return payload.hasOwnProperty('variableName'); } function contains_variableValue(payload) { if (!payload.hasOwnProperty('variableValue')) node.warn("Optional parameter 'variableValue' missing") return payload.hasOwnProperty('variableValue'); } function contains_necessaryProperties(msg) { if (contains_messageType(msg.payload)) { return(contains_namespace(msg.payload) && contains_variableName(msg.payload) && contains_variableValue(msg.payload)); } else { if (msg.payload.hasOwnProperty('opcuaCommand') && msg.payload.opcuaCommand === "addVariable") { // msg.topic with nodeId and datatype if (msg.topic.indexOf("ns=")>=0 && msg.topic.indexOf("datatype=")>0) { return true; } else { node.warn("msg.topic must contain nodeId and datatype!"); } } else { return contains_opcua_command(msg.payload); } } } function read_message(payload) { switch (payload.messageType) { case 'Variable': var ns = payload.namespace.toString(); const variableId = `${ns}:${payload.variableName}` verbose_log("BEFORE: " + ns + ":" + payload.variableName + " value: " + JSON.stringify(variables[variableId])); var value = payload.variableValue; if (payload.variableValue === "true" || payload.variableValue === true || payload.variableValue === 1) { value = true; } if (payload.variableValue === "false" || payload.variableValue === false || payload.variableValue === 0) { value = false; } variables[variableId] = value; // update server variable value if needed now variables[variableId]=value used var addressSpace = node.server.engine.addressSpace; // var vnode = addressSpace.findNode("ns="+ns+";s="+ payload.variableName); if(typeof(payload.variableName) === 'number') { verbose_log("findNode(ns="+ns+";i="+payload.variableName); var vnode = addressSpace.findNode("ns="+ns+";i="+payload.variableName); if(vnode === null) { verbose_warn("vnode is null, findNode did not succeeded"); } } else { // if( typeof(payload.variableName)==='string') // this must be string - a plain variable name // TODO opaque verbose_log("findNode ns="+ns+";s="+payload.variableName); var vnode = addressSpace.findNode("ns="+ns+";s="+payload.variableName); } if (vnode) { verbose_log("Found variable, nodeId: " + vnode.nodeId); variables[variableId] = opcuaBasics.build_new_value_by_datatype(payload.datatype, payload.variableValue); // var newValue = opcuaBasics.build_new_variant(payload.datatype, payload.variableValue); var newValue = opcuaBasics.build_new_dataValue(payload.datatype, payload.variableValue); vnode.setValueFromSource(newValue); // This fixes if variable if not bound eq. bindVariables is not called if (payload.quality && payload.sourceTimestamp) { // var statusCode = opcua.StatusCodes.BadDeviceFailure; // var statusCode = opcua.StatusCodes.BadDataLost; // Bad 0x80000000 if(typeof(payload.quality)==='string') { // a name of Quality was given -> convert it to number verbose_log("Getting numeric status code of quality: " + payload.quality); payload.quality = opcua.StatusCodes[payload.quality].value; } // else // typeof(payload.quality)==='number', e.g. 2161770496 var statusCode = opcua.StatusCode.makeStatusCode(payload.quality); verbose_log("StatusCode from value: " + payload.quality + " (0x" + payload.quality.toString(16) + ") description: " + statusCode.description); var ts = new Date(payload.sourceTimestamp); verbose_log("Timestamp: " + ts.toISOString()); verbose_log("Set variable, newValue:" + JSON.stringify(newValue) + " statusCode: " + statusCode.description + " sourceTimestamp: " + ts); vnode.setValueFromSource(newValue, statusCode, ts); // Dummy & quick fix for statusCode & timeStamp, look timestamped_get variablesStatus[variableId] = statusCode; variablesTs[variableId] = ts; // console.log("Statuscode & sourceTimestamp, vnode: " + JSON.stringify(vnode)); var session = new opcua.PseudoSession(addressSpace); const nodesToWrite = [ { nodeId: vnode.nodeId, attributeId: opcua.AttributeIds.Value, value: new opcua.DataValue( { value: newValue, statusCode: statusCode, sourceTimestamp: ts }) } ]; // verbose_log("Write: " + JSON.stringify(nodesToWrite)); session.write(nodesToWrite, function (err, statusCodes) { if (err) { node.error("Write error: " + err); } else { // verbose_log("Write succeeded, statusCode: " + JSON.stringify(statusCodes)); } }); /* // NOT WORKING SOLUTION EVEN IT WAS CLEAN else { verbose_log("Set variable, newValue:" + JSON.stringify(newValue) + " statusCode: " + statusCode.description); vnode.setValueFromSource(newValue, statusCode); console.log("Statuscode, vnode: " + JSON.stringify(vnode)); vnode.setValueFromSource(newValue, statusCode); } */ } } else { node.error("Variable not found from server address space: " + payload.namespace + ":" + payload.variableName); } verbose_log("AFTER : " + ns + ":" + payload.variableName + " value: " + JSON.stringify(variables[variableId])); break; default: break; } } function contains_opcua_command(payload) { return payload.hasOwnProperty('opcuaCommand'); } function execute_opcua_command(msg) { var payload = msg.payload; var addressSpace = node.server.engine.addressSpace; var name; var returnValue = ""; switch (payload.opcuaCommand) { case "restartOPCUAServer": restart_server(); break; case "addEquipment": verbose_log(chalk.cyan("Adding node: ".concat(payload.nodeName))); equipmentCounter++; name = payload.nodeName.concat(equipmentCounter); addressSpace.getOwnNamespace().addObject({ organizedBy: addressSpace.findNode(equipment.nodeId), nodeId: "ns=1;s=".concat(name), browseName: name }); break; case "addPhysicalAsset": verbose_log(chalk.cyan("Adding node: ".concat(payload.nodeName))); physicalAssetCounter++; name = payload.nodeName.concat(physicalAssetCounter); addressSpace.getOwnNamespace().addObject({ organizedBy: addressSpace.findNode(physicalAssets.nodeId), nodeId: "ns=1;s=".concat(name), browseName: name }); break; case "setFolder": verbose_log(chalk.cyan("Set Folder: ".concat(msg.topic))); // Example topic format ns=4;s=FolderName folder = addressSpace.findNode(msg.topic); if (folder) { verbose_log(chalk.yellow("Found folder: ") + chalk.cyan(folder)); } else { verbose_warn(chalk.red("Folder not found for topic: ") + chalk.cyan(msg.topic)); } break; case "addFolder": var nodeId = msg.topic; var description = ""; var d = msg.topic.indexOf("description="); if (d > 0) { nodeId = nodeId.substring(0, d - 1); // format is msg.topic="ns=1;s=TEST;description=MyTestFolder" description = msg.topic.substring(d + 12); if (description.indexOf(";") >= 0) { description = description.substring(0, description.indexOf(";")); } } var browseName = ""; var d = msg.topic.indexOf("browseName="); if (d > 0) { nodeId = msg.topic.substring(0, d - 1); // format is msg.topic="ns=1;s=Main.Test;browseName=Test" browseName = msg.topic.substring(d + 11); if (browseName.indexOf(";") >= 0) { browseName = browseName.substring(0, browseName.indexOf(";")); verbose_log("Folder browseName: " + browseName); } } verbose_log("Adding Folder: ".concat(nodeId)); // Example topic format ns=4;s=FolderName var parentFolder = node.server.engine.addressSpace.rootFolder.objects; if (folder) { parentFolder = folder; // Use previously created folder as parentFolder or setFolder() can be used to set parentFolder } // Check & add from msg accessLevel userAccessLevel, role & permissions var accessLevel = opcua.makeAccessLevelFlag("CurrentRead|CurrentWrite"); // Use as default var userAccessLevel = opcua.makeAccessLevelFlag("CurrentRead|CurrentWrite"); // Use as default if (msg.accessLevel) { accessLevel = msg.accessLevel; } if (msg.userAccessLevel) { userAccessLevel = msg.userAccessLevel; } // permissions collected from multiple opcua-rights let permissions = [ { roleId: opcua.WellKnownRoles.Anonymous, permissions: opcua.allPermissions }, { roleId: opcua.WellKnownRoles.AuthenticatedUser, permissions: opcua.allPermissions }, ]; if (msg.permissions) { permissions = msg.permissions; } // Own namespace if (nodeId.indexOf("ns=1;") >= 0) { parentFolder = node.server.engine.addressSpace.rootFolder.objects; if (folder) { parentFolder = folder; // Use previously created folder as parentFolder or setFolder() can be used to set parentFolder } if (browseName.length === 0) { browseName = nodeId.substring(7); } console.log("### nodeId: " + nodeId + " parent: " + parentFolder.nodeId + " description: '" + description + "' browseName: '" + browseName + "'"); folder = addressSpace.getOwnNamespace().addObject({ organizedBy: addressSpace.findNode(parentFolder.nodeId), nodeId: nodeId, // msg.topic, description: description || "", accessLevel: accessLevel, // TEST more userAccessLevel: userAccessLevel, // TEST more rolePermissions: [].concat(permissions), accessRestrictions: opcua.AccessRestrictionsFlag.None, // TODO from msg browseName: browseName // || nodeId.substring(7) // browseName cannot be empty }); } else { verbose_log("Topic: " + nodeId + " index: " + nodeId.substring(3)); const index = parseInt(nodeId.substring(3)); verbose_log("ns index: " + index); const uri = addressSpace.getNamespaceUri(index); verbose_log("ns uri: " + uri); const ns = addressSpace.getNamespace(uri); // Or index var name = nodeId; // msg.topic; var browseName = name; // Use full name by default // NodeId can be string or integer or Guid or Opaque: ns=10;i=1000 or ns=5;g= var bIndex = name.indexOf(";s="); // String if (bIndex>0) { browseName = name.substring(bIndex+3); } bIndex = name.indexOf(";i="); // Integer if (bIndex>0) { browseName = name.substring(bIndex+3); } bIndex = name.indexOf(";g="); // Guid if (bIndex>0) { browseName = name.substring(bIndex+3); } bIndex = name.indexOf(";b="); // Opaque base64 if (bIndex>0) { browseName = name.substring(bIndex+3); } folder = ns.addObject({ organizedBy: addressSpace.findNode(parentFolder.nodeId), nodeId: nodeId, // msg.topic, description: description, accessLevel: accessLevel, // TEST more userAccessLevel: userAccessLevel, // TEST more rolePermissions: [].concat(permissions), accessRestrictions: opcua.AccessRestrictionsFlag.None, // TODO from msg browseName: browseName // msg.topic.substring(msg.topic.indexOf(";s=")+3) }) } break; case "addVariable": verbose_log("Adding node: ".concat(msg.topic)); // Example topic format ns=4;s=VariableName;datatype=Double var datatype = ""; var description = ""; var displayName = ""; var browseNameTopic = ""; var opcuaDataType = null; var e = msg.topic.indexOf("datatype="); if (e<0) { node_error("no datatype=Float or other type in addVariable ".concat(msg.topic)); // Example topic format ns=4;s=FolderName } var parentFolder = node.server.engine.addressSpace.rootFolder.objects; if (folder != null) { parentFolder = folder; // Use previous folder as parent or setFolder() can be use to set parent } var d = msg.topic.indexOf("description="); if (d > 0) { description = msg.topic.substring(d + 12); if (description.indexOf(";") >= 0) { description = description.substring(0, description.indexOf(";")); } } var d = msg.topic.indexOf("browseName="); if (d > 0) { browseNameTopic = msg.topic.substring(d + 11); if (browseNameTopic.indexOf(";") >= 0) { browseNameTopic = browseNameTopic.substring(0, browseNameTopic.indexOf(";")); } } const dn = msg.topic.indexOf("displayName="); if (dn > 0) { displayName = msg.topic.substring(dn + 12); // console.log(displayName); if (displayName.indexOf(";") >= 0) { displayName = displayName.substring(0, displayName.indexOf(";")); } } if (e > 0) { name = msg.topic.substring(0, e - 1); datatype = msg.topic.substring(e + 9); // ExtentionObject contains extra info like typeId if (datatype.indexOf(";") >= 0) { datatype = datatype.substring(0, datatype.indexOf(";")); } var arrayType = opcua.VariantArrayType.Scalar; var arr = datatype.indexOf("Array"); var dim1 = 0; // Fix for the scalars var dim2 = 0; // Matrix var dim3 = 0; // Cube var indexStr = ""; var valueRank =