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
JavaScript
/**
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 =