node-red-contrib-iiot-opcua
Version:
An Industrial IoT OPC UA toolbox contribution package for Node-RED based on node-opcua.
1,054 lines • 53.8 kB
JavaScript
/*
The BSD 3-Clause License
Copyright 2022 - DATATRONiQ GmbH (https://datatroniq.com)
Copyright (c) 2018-2022 Klaus Landsdorf (http://node-red.plus/)
Copyright 2015,2016 - Mika Karaila, Valmet Automation Inc. (node-red-contrib-opcua)
All rights reserved.
node-red-contrib-iiot-opcua
*/
'use strict';
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const path = __importStar(require("path"));
const opcua_iiot_core_1 = require("./core/opcua-iiot-core");
const node_opcua_1 = require("node-opcua");
const opcua_iiot_core_connector_1 = __importStar(require("./core/opcua-iiot-core-connector"));
const underscore_1 = __importStar(require("underscore"));
const node_opcua_service_endpoints_1 = require("node-opcua-service-endpoints");
var internalDebugLog = opcua_iiot_core_connector_1.logger.internalDebugLog;
var detailDebugLog = opcua_iiot_core_connector_1.logger.detailDebugLog;
const helpers_1 = require("./types/helpers");
/**
* OPC UA connector Node-RED config this.
*
* @param RED
*/
module.exports = function (RED) {
// SOURCE-MAP-REQUIRED
function OPCUAIIoTConnectorConfiguration(config) {
var _a;
const CONNECTION_START_DELAY = 2000; // msec.
const CONNECTION_STOP_DELAY = 2000; // msec.
const RECONNECT_DELAY = 1000; // msec.
const UNLIMITED_LISTENERS = 0;
RED.nodes.createNode(this, config);
// HTML settings
this.discoveryUrl = config.discoveryUrl || null;
this.endpoint = config.endpoint;
this.endpointMustExist = config.endpointMustExist || false;
this.keepSessionAlive = config.keepSessionAlive;
this.loginEnabled = config.loginEnabled;
this.name = config.name;
this.showErrors = config.showErrors;
this.securityPolicy = (0, node_opcua_1.coerceSecurityPolicy)(config.securityPolicy);
this.messageSecurityMode = (0, node_opcua_1.coerceMessageSecurityMode)(config.securityMode) || node_opcua_1.MessageSecurityMode.None;
this.individualCerts = config.individualCerts;
this.publicCertificateFile = config.publicCertificateFile;
this.privateKeyFile = config.privateKeyFile;
this.defaultSecureTokenLifetime = config.defaultSecureTokenLifetime || 120000;
this.autoSelectRightEndpoint = config.autoSelectRightEndpoint;
this.strategyMaxRetry = config.strategyMaxRetry || 10000;
this.strategyInitialDelay = config.strategyInitialDelay || 1000;
this.strategyMaxDelay = parseInt(config.strategyMaxDelay) || 30000;
this.strategyRandomisationFactor = config.strategyRandomisationFactor || 0.2;
this.requestedSessionTimeout = config.requestedSessionTimeout || 60000;
this.connectionStartDelay = config.connectionStartDelay || CONNECTION_START_DELAY;
this.reconnectDelay = config.reconnectDelay || RECONNECT_DELAY;
this.connectionStopDelay = config.connectionStopDelay || CONNECTION_STOP_DELAY;
this.maxBadSessionRequests = parseInt((_a = config.maxBadSessionRequests) === null || _a === void 0 ? void 0 : _a.toString()) || 10;
this.iiot = opcua_iiot_core_connector_1.default.initConnectorNode();
if (!this.iiot)
throw Error('IIoT Initialization Failed');
this.setMaxListeners(UNLIMITED_LISTENERS);
const self = this;
internalDebugLog('Open Connector Node');
let sessionStartTimeout;
let clientStartTimeout;
let disconnectTimeout;
let nodeOPCUAClientPath = (0, opcua_iiot_core_1.getNodeOPCUAClientPath)();
this.securedCommunication = (this.securityPolicy !== node_opcua_1.SecurityPolicy.None &&
this.messageSecurityMode !== node_opcua_1.MessageSecurityMode.None);
detailDebugLog('config: ' + this.publicCertificateFile);
detailDebugLog('config: ' + this.privateKeyFile);
detailDebugLog('securedCommunication: ' + this.securedCommunication.toString());
const initCertificatesAndKeys = () => {
if (this.securedCommunication) {
this.publicCertificateFile = this.publicCertificateFile || path.join(nodeOPCUAClientPath, '/certificates/client_selfsigned_cert_1024.pem');
detailDebugLog('using cert: ' + this.publicCertificateFile);
this.privateKeyFile = this.privateKeyFile || path.join(nodeOPCUAClientPath, '/certificates/PKI/own/private/private_key.pem');
detailDebugLog('using key: ' + this.privateKeyFile);
}
else {
this.publicCertificateFile = null;
this.privateKeyFile = null;
}
};
if (this.loginEnabled) {
if (this.credentials) {
this.iiot.userIdentity = {
type: node_opcua_service_endpoints_1.UserTokenType.UserName,
userName: this.credentials.user,
password: this.credentials.password
};
internalDebugLog('Connecting With Login Data On ' + this.endpoint);
}
else {
/* istanbul ignore next */
this.error(new Error('Login Enabled But No Credentials'), { payload: '' });
}
}
/* ######### CONNECTION ######### */
const getUpdatedServerOptions = () => {
var _a, _b;
initCertificatesAndKeys();
return {
securityPolicy: this.securityPolicy,
securityMode: this.messageSecurityMode,
defaultSecureTokenLifetime: this.defaultSecureTokenLifetime,
keepSessionAlive: this.keepSessionAlive,
certificateFile: (_a = this.publicCertificateFile) !== null && _a !== void 0 ? _a : undefined,
privateKeyFile: (_b = this.privateKeyFile) !== null && _b !== void 0 ? _b : undefined,
endpointMustExist: this.endpointMustExist,
requestedSessionTimeout: this.requestedSessionTimeout,
connectionStrategy: {
maxRetry: this.strategyMaxRetry,
initialDelay: this.strategyInitialDelay,
maxDelay: this.strategyMaxDelay,
randomisationFactor: this.strategyRandomisationFactor
}
};
};
const statusHandler = (status) => {
this.status(status);
};
const errorHandler = (err) => {
this.error(err);
};
const connectOPCUAEndpoint = () => __awaiter(this, void 0, void 0, function* () {
if ((0, underscore_1.isUndefined)(this.iiot))
return;
if (!opcua_iiot_core_connector_1.default.checkEndpoint(this.endpoint, errorHandler)) {
return;
}
internalDebugLog('Connecting To Endpoint ' + this.endpoint);
this.iiot.opcuaClientOptions = getUpdatedServerOptions();
if (!this.iiot.opcuaClient)
this.iiot.opcuaClient = node_opcua_1.OPCUAClient.create(Object.assign({}, this.iiot.opcuaClientOptions)); // Need to use the spread operator, because otherwise there is phantom circular references
if (Object.keys(this.iiot.opcuaClient).length === 0) {
/* istanbul ignore next */
detailDebugLog('Failed to create OPCUA Client ', { opcuaClient: this.iiot.opcuaClient });
}
if (this.autoSelectRightEndpoint) {
autoSelectEndpointFromConnection();
}
// coreConnector.setListenerToClient(node)
connectToClient();
});
const connectToClient = () => {
if ((0, underscore_1.isUndefined)(this.iiot))
return;
if ((0, underscore_1.isUndefined)(this.iiot.stateService))
return;
// Needs to be separate if so that typescript understands the types properly
if ((0, underscore_1.isUndefined)(this.iiot.opcuaClient))
return;
if (!opcua_iiot_core_connector_1.default.checkEndpoint(this.endpoint, errorHandler)) {
return;
}
this.iiot.opcuaClient.connect(this.endpoint, (err) => {
var _a;
if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this) && !(0, underscore_1.isUndefined)(this.iiot) && !(0, underscore_1.isUndefined)(this.iiot.stateService)) {
if (err) {
//this.iiot.stateMachine.lock().stopopcua()
(_a = this.iiot) === null || _a === void 0 ? void 0 : _a.stateService.send('STOP');
handleError(err);
}
else {
internalDebugLog('Client Is Connected To ' + this.endpoint);
//this.iiot.stateMachine.open()
this.iiot.stateService.send('OPEN');
}
}
else {
/* istanbul ignore next */
internalDebugLog('iiot not valid on connect resolve');
}
});
};
const renewConnection = (done) => {
if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
opcuaDirectDisconnect(() => {
if ((0, underscore_1.isUndefined)(this.iiot))
return;
renewFiniteStateMachine();
//this.iiot.stateMachine.idle().initopcua();
// Todo: the steps have to be used as before
this.iiot.stateService.send('IDLE');
this.iiot.stateService.send('INITOPCUA');
done();
});
}
else {
/* istanbul ignore next */
internalDebugLog('iiot not valid on renew connection');
}
};
const endpointMatchForConnecting = (endpoint) => {
var _a, _b, _c;
internalDebugLog('Auto Endpoint ' + ((_a = endpoint.endpointUrl) === null || _a === void 0 ? void 0 : _a.toString()) + ' ' + ((_b = endpoint.securityPolicyUri) === null || _b === void 0 ? void 0 : _b.toString()));
let securityMode = endpoint.securityMode;
let securityPolicy = ((_c = endpoint.securityPolicyUri) === null || _c === void 0 ? void 0 : _c.includes('SecurityPolicy#')) ? endpoint.securityPolicyUri.split('#')[1] : endpoint.securityPolicyUri;
internalDebugLog('node-mode:' + this.messageSecurityMode + ' securityMode: ' + securityMode);
internalDebugLog('node-policy:' + this.securityPolicy + ' securityPolicy: ' + securityPolicy);
return (securityMode === this.messageSecurityMode && securityPolicy === this.securityPolicy);
};
const selectEndpointFromSettings = (discoverClient) => {
discoverClient.getEndpoints((err, endpoints) => {
if (err) {
/* istanbul ignore next */
internalDebugLog('Auto Switch To Endpoint Error ' + err);
if (this.showErrors) {
this.error(err, { payload: 'Get Endpoints Request Error' });
}
}
else {
const endpoint = (endpoints || []).find((endpoint) => {
endpointMatchForConnecting(endpoint);
});
if (endpoint && endpoint.endpointUrl != null) {
/* istanbul ignore next */
internalDebugLog('Auto Switch To Endpoint ' + endpoint.endpointUrl);
this.endpoint = endpoint.endpointUrl;
}
else {
internalDebugLog('Auto Switch To Endpoint failed: no valid endpoints');
}
}
discoverClient.disconnect((err) => {
if (err) {
/* istanbul ignore next */
internalDebugLog('Endpoints Auto Request Error ' + err);
if (this.showErrors) {
/* istanbul ignore next */
this.error(err, { payload: 'Discover Client Disconnect Error' });
}
}
else {
internalDebugLog('Endpoints Auto Request Done With Endpoint ' + this.endpoint);
}
});
});
};
const autoSelectEndpointFromConnection = () => {
internalDebugLog('Auto Searching For Endpoint On ' + this.endpoint);
if ((0, underscore_1.isUndefined)(this.iiot) || (0, underscore_1.isUndefined)(this.iiot.opcuaClientOptions))
return;
let endpointMustExist = this.iiot.opcuaClientOptions.endpointMustExist;
this.iiot.opcuaClientOptions.endpointMustExist = false;
let discoverClient = node_opcua_1.OPCUAClient.create(this.iiot.opcuaClientOptions);
discoverClient.connect(this.endpoint).then(() => {
internalDebugLog('Auto Searching Endpoint Connected To ' + this.endpoint);
selectEndpointFromSettings(discoverClient);
if ((0, underscore_1.isUndefined)(this.iiot) || (0, underscore_1.isUndefined)(this.iiot.opcuaClientOptions))
return;
this.iiot.opcuaClientOptions.endpointMustExist = endpointMustExist;
}).catch((err) => {
/* istanbul ignore next */
internalDebugLog('Get Auto Endpoint Request Error ' + err.message);
if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot) && !(0, underscore_1.isUndefined)(this.iiot.opcuaClientOptions)) {
this.iiot.opcuaClientOptions.endpointMustExist = endpointMustExist;
}
});
};
/* ######### SESSION ######### */
const startSession = (callerInfo) => __awaiter(this, void 0, void 0, function* () {
internalDebugLog('Request For New Session From ' + callerInfo);
if ((0, underscore_1.isUndefined)(this.iiot)) {
/* istanbul ignore next */
return;
}
if (isInactiveOnOPCUA()) {
internalDebugLog('State Is Not Active While Start Session-> ' + this.iiot.stateService.state.value);
if (this.showErrors) {
/* istanbul ignore next */
this.error(new Error('OPC UA Connector Is Not Active'), { payload: 'Create Session Error' });
}
return;
}
if (this.iiot.stateService.state.value !== opcua_iiot_core_1.FsmConnectorStates.StateOpened) {
internalDebugLog('Session Request Not Allowed On State ' + this.iiot.stateService.state.value);
if (this.showErrors) {
/* istanbul ignore next */
this.error(new Error('OPC UA Connector Is Not Open'), { payload: 'Create Session Error' });
}
return;
}
if (!this.iiot.opcuaClient) {
internalDebugLog('OPC UA Client Connection Is Not Valid On State ' + this.iiot.stateService.state.value);
if (this.showErrors) {
/* istanbul ignore next */
this.error(new Error('OPC UA Client Connection Is Not Valid'), { payload: 'Create Session Error' });
}
return;
}
//this.iiot.stateMachine.sessionrequest()
this.iiot.stateService.send('SESSIONREQUEST');
const res = yield this.iiot.opcuaClient.createSession(this.iiot.userIdentity)
.then((session) => {
var _a;
if ((0, underscore_1.isUndefined)(this.iiot))
return;
session.requestedMaxReferencesPerNode = 100000;
this.iiot.opcuaSession = session;
//this.iiot.stateMachine.sessionactive()
this.iiot.stateService.send('SESSIONACTIVATE');
detailDebugLog('Session Created On ' + this.endpoint + ' For ' + callerInfo);
opcua_iiot_core_connector_1.default.logSessionInformation(this);
(_a = this.iiot.opcuaSession) === null || _a === void 0 ? void 0 : _a.on('session_closed', (statusCode) => {
handleSessionClose(statusCode);
});
}).catch((err) => {
/* istanbul ignore next */
if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
//this.iiot.stateMachine.lock().stopopcua()
this.iiot.stateService.send('LOCK');
this.iiot.stateService.send('STOP');
handleError(err);
}
else {
internalDebugLog(err.message);
}
this.emit('session_error', err);
return -1;
});
});
const resetBadSession = () => {
if (!this.iiot) {
/* istanbul ignore next */
return;
}
this.iiot.sessionNodeRequests += 1;
detailDebugLog('Session Node Requests At Connector No.: ' + this.iiot.sessionNodeRequests);
if (this.showErrors) {
/* istanbul ignore next */
internalDebugLog('!!!!!!!!!!!!!!!!!!!!! BAD SESSION ON CONNECTOR !!!!!!!!!!!!!!!!!!');
}
if (this.iiot.sessionNodeRequests > this.maxBadSessionRequests) {
internalDebugLog('Reset Bad Session Request On State ' + this.iiot.stateService.state.value);
resetOPCUAConnection('ToManyBadSessionRequests');
}
};
const isInactiveOnOPCUA = () => {
var _a, _b;
//let state = this.iiot?.stateMachine?.getMachineState()
let state = (_b = (_a = this.iiot) === null || _a === void 0 ? void 0 : _a.stateService) === null || _b === void 0 ? void 0 : _b.state.value;
return (state === opcua_iiot_core_1.FsmConnectorStates.StateStopped ||
state === opcua_iiot_core_1.FsmConnectorStates.StateEnd ||
state === opcua_iiot_core_1.FsmConnectorStates.StateRenewed ||
state === opcua_iiot_core_1.FsmConnectorStates.StateReconfigured);
};
const resetOPCUAConnection = (callerInfo) => {
detailDebugLog(callerInfo + ' Request For New OPC UA Connection');
if (isInactiveOnOPCUA() || (0, underscore_1.isUndefined)(this.iiot)) {
return;
}
//this.iiot.stateMachine.lock().renew()
this.iiot.stateService.send('LOCK');
this.iiot.stateService.send('RENEW');
this.emit('reset_opcua_connection');
closeSession(() => {
renewConnection(() => {
detailDebugLog('OPC UA Connection Reset Done');
});
});
};
const handleError = (err) => {
internalDebugLog('Handle Error On ' + this.endpoint + ' err: ' + err);
if (this.showErrors) {
/* istanbul ignore next */
this.error(err, { payload: 'Handle Connector Error' });
}
};
const closeSession = (done) => {
var _a, _b;
if ((0, underscore_1.isUndefined)(this.iiot) || underscore_1.default.isEmpty(this.iiot)) {
/* istanbul ignore next */
done();
return;
}
if (((_a = this.iiot) === null || _a === void 0 ? void 0 : _a.opcuaClient) && ((_b = this.iiot) === null || _b === void 0 ? void 0 : _b.opcuaSession)) {
detailDebugLog('Close Session And Remove Subscriptions From Session On State ' + this.iiot.stateService.state.value);
try {
this.iiot.opcuaSession.removeAllListeners();
this.iiot.opcuaClient.closeSession(this.iiot.opcuaSession, this.iiot.hasOpcUaSubscriptions, (err) => {
if (err) {
handleError(err);
}
done();
});
}
catch (err) {
/* istanbul ignore next */
handleError(err);
done();
}
finally {
if (this.iiot)
this.iiot.opcuaSession = undefined;
}
}
else {
internalDebugLog('Close Session Without Session On State ' + this.iiot.stateService.state.value);
done();
}
};
const hasNoSession = () => {
var _a, _b;
return underscore_1.default.isUndefined(this.iiot) || underscore_1.default.isUndefined((_a = this.iiot) === null || _a === void 0 ? void 0 : _a.opcuaSession) || underscore_1.default.isNull((_b = this.iiot) === null || _b === void 0 ? void 0 : _b.opcuaSession);
};
const hasSession = () => {
return !hasNoSession();
};
const handleSessionClose = (statusCode) => {
var _a;
internalDebugLog('Session Closed With StatusCode ' + statusCode);
if ((0, underscore_1.isUndefined)(this.iiot)) {
return;
}
if (isInactiveOnOPCUA()) {
detailDebugLog('Connector Is Not Active On OPC UA While Session Close Event');
return;
}
opcua_iiot_core_connector_1.default.logSessionInformation(this);
if (((_a = this.iiot) === null || _a === void 0 ? void 0 : _a.stateMachine) && this.iiot.stateService.state.value !== opcua_iiot_core_1.FsmConnectorStates.StateSessionRestart) {
//this.iiot.stateMachine.lock().sessionclose()
this.iiot.stateService.send('LOCK');
this.iiot.stateService.send('SESSIONCLOSE');
}
};
const disconnectNodeOPCUA = (done) => {
var _a;
if ((0, underscore_1.isUndefined)(this.iiot) || underscore_1.default.isEmpty(this.iiot)) {
done();
return;
}
internalDebugLog('OPC UA Disconnect Connector On State ' + this.iiot.stateService.state.value);
if ((_a = this.iiot) === null || _a === void 0 ? void 0 : _a.opcuaClient) {
internalDebugLog('Close Node Disconnect Connector From ' + this.endpoint);
try {
this.iiot.opcuaClient.disconnect((err) => {
if (err) {
handleError(err);
}
internalDebugLog('Close Node Done For Connector On ' + this.endpoint);
done();
});
}
catch (err) {
handleError(err);
done();
}
finally {
this.iiot.opcuaClient = undefined;
}
}
else {
internalDebugLog('Close Node Done For Connector Without Client On ' + this.endpoint);
done();
}
};
this.on('close', (done) => {
self.removeAllListeners();
if ((0, underscore_1.isUndefined)(this.iiot) || underscore_1.default.isEmpty(this.iiot)) {
done();
return;
}
if (!(0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
done(); // if we have a very fast deploy clicking uer
}
else {
if (isInactiveOnOPCUA()) {
detailDebugLog('OPC UA Client Is Not Active On Close Node');
(0, opcua_iiot_core_1.resetIiotNode)(this);
done();
}
else {
detailDebugLog('OPC UA Client Is Active On Close Node With State ' + this.iiot.stateService.state.value);
if (this.iiot.stateService.state.value === opcua_iiot_core_1.FsmConnectorStates.StateSessionActive) {
closeConnector(() => {
(0, opcua_iiot_core_1.resetIiotNode)(this);
done();
});
}
else {
internalDebugLog(this.iiot.stateService.state.value + ' -> !!! CHECK CONNECTOR STATE ON CLOSE !!!');
(0, opcua_iiot_core_1.resetIiotNode)(this);
done();
}
}
}
});
const opcuaDisconnect = (done) => {
if ((0, underscore_1.isUndefined)(this.iiot) ||
(0, underscore_1.isUndefined)(this.iiot.registeredNodeList) ||
underscore_1.default.isEmpty(this.iiot) ||
underscore_1.default.isArray(this.iiot.registeredNodeList) === false) {
opcuaDirectDisconnect(done);
return;
}
if (Object.keys(this.iiot.registeredNodeList).length > 0) {
internalDebugLog('Connector Has Registered Nodes And Can Not Close The Node -> Count: ' + this.iiot.registeredNodeList.length);
if (disconnectTimeout) {
clearTimeout(disconnectTimeout);
disconnectTimeout = null;
}
disconnectTimeout = setTimeout(() => {
if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
closeConnector(done);
}
}, this.connectionStopDelay);
}
else {
opcuaDirectDisconnect(done);
}
};
const opcuaDirectDisconnect = (done) => {
if ((0, underscore_1.isUndefined)(this.iiot) || underscore_1.default.isEmpty(this.iiot)) {
done();
return;
}
detailDebugLog('OPC UA Disconnect From Connector ' + this.iiot.stateService.state.value);
disconnectNodeOPCUA(() => {
if ((0, underscore_1.isUndefined)(this.iiot)) {
done();
return;
}
//this.iiot.stateMachine.lock().close()
this.iiot.stateService.send('LOCK');
this.iiot.stateService.send('CLOSE');
let fsmState = this.iiot.stateService.state.value;
detailDebugLog('Disconnected On State ' + fsmState);
// if (!isInactiveOnOPCUA() && fsmState !== 'CLOSED') { //Todo: check state machine to be closed
// return;
// }
done();
});
};
const closeConnector = (done) => {
if ((0, underscore_1.isUndefined)(this.iiot)) {
return;
}
detailDebugLog('Close Connector ' + this.iiot.stateService.value);
if (isInactiveOnOPCUA()) {
detailDebugLog('OPC UA Client Is Not Active On Close Connector');
done();
return;
}
if (this.iiot.opcuaClient) {
opcuaDisconnect(done);
}
else {
detailDebugLog('OPC UA Client Is Not Valid On Close Connector');
done();
}
};
const restartWithNewSettings = (config, done) => {
if ((0, underscore_1.isUndefined)(this.iiot)) {
return;
}
internalDebugLog('Renew With Flex Connector Request On State ' + this.iiot.stateService.state.value);
//this.iiot.stateMachine.lock().reconfigure()
this.iiot.stateService.send('LOCK');
this.iiot.stateService.send('RECONFIGURE');
updateSettings(config);
initCertificatesAndKeys();
renewConnection(done);
};
const normalizeCapitalization = (input) => {
if (!input.length)
return input;
return input[0].toUpperCase() + input.substring(1).toLowerCase();
};
const updateSettings = (config) => {
this.discoveryUrl = config.discoveryUrl || this.discoveryUrl;
this.endpoint = config.endpoint || this.endpoint;
this.keepSessionAlive = config.keepSessionAlive || this.keepSessionAlive;
this.securityPolicy = (0, node_opcua_1.coerceSecurityPolicy)(config.securityPolicy || this.securityPolicy);
this.messageSecurityMode = (0, node_opcua_1.coerceMessageSecurityMode)(normalizeCapitalization((config.securityMode || this.messageSecurityMode)));
this.name = config.name || this.name;
this.showErrors = config.showErrors || this.showErrors;
this.publicCertificateFile = config.publicCertificateFile || this.publicCertificateFile;
this.privateKeyFile = config.privateKeyFile || this.privateKeyFile;
this.defaultSecureTokenLifetime = config.defaultSecureTokenLifetime || this.defaultSecureTokenLifetime;
this.endpointMustExist = config.endpointMustExist || this.endpointMustExist;
this.autoSelectRightEndpoint = config.autoSelectRightEndpoint || this.autoSelectRightEndpoint;
this.strategyMaxRetry = config.strategyMaxRetry || this.strategyMaxRetry;
this.strategyInitialDelay = config.strategyInitialDelay || this.strategyInitialDelay;
this.strategyMaxDelay = parseInt(config.strategyMaxDelay) || this.strategyMaxDelay;
this.strategyRandomisationFactor = config.strategyRandomisationFactor || this.strategyRandomisationFactor;
this.requestedSessionTimeout = config.requestedSessionTimeout || this.requestedSessionTimeout;
this.connectionStartDelay = config.connectionStartDelay || this.connectionStartDelay;
this.reconnectDelay = config.reconnectDelay || this.reconnectDelay;
};
const resetOPCUAObjects = () => {
var _a, _b;
if ((0, underscore_1.isUndefined)(this.iiot)) {
return;
}
detailDebugLog('Reset All OPC UA Objects');
this.iiot.sessionNodeRequests = 0;
if (this.iiot.opcuaSession) {
if (this.iiot.opcuaClient) {
this.iiot.opcuaClient.closeSession(this.iiot.opcuaSession, true);
}
this.iiot.opcuaSession.removeAllListeners();
this.iiot.opcuaSession = undefined;
}
if (Object.keys(this.iiot.opcuaClient || {}).length > 1) {
(_a = this.iiot.opcuaClient) === null || _a === void 0 ? void 0 : _a.removeAllListeners();
(_b = this.iiot.opcuaClient) === null || _b === void 0 ? void 0 : _b.disconnect((err) => {
if (err && !(0, underscore_1.isUndefined)(this.iiot)) {
handleError(err);
}
});
this.iiot.opcuaClient = undefined;
}
};
/* ######### FSM ######### */
const connectorStateEventFunction = (state) => __awaiter(this, void 0, void 0, function* () {
var _b, _c;
if (!state.changed)
return;
if (this.iiot === undefined)
return;
switch (state.value) {
case opcua_iiot_core_1.FsmConnectorStates.StateIdle:
detailDebugLog('Connector IDLE Event FSM');
resetOPCUAObjects();
break;
case opcua_iiot_core_1.FsmConnectorStates.StateInit:
detailDebugLog('Connector Init OPC UA Event FSM');
if (!this.iiot) {
return;
}
resetOPCUAObjects();
resetAllTimer();
this.emit('connector_init', this);
initCertificatesAndKeys();
if (clientStartTimeout) {
clearTimeout(clientStartTimeout);
clientStartTimeout = null;
}
detailDebugLog('connecting OPC UA with delay of msec: ' + this.connectionStartDelay);
clientStartTimeout = setTimeout(() => {
if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
try {
connectOPCUAEndpoint();
}
catch (err) {
handleError(err);
resetOPCUAObjects();
//this.iiot.stateMachine.lock().stopopcua()
this.iiot.stateService.send('LOCK');
this.iiot.stateService.send('STOP');
}
}
}, this.connectionStartDelay);
break;
case opcua_iiot_core_1.FsmConnectorStates.StateOpened:
detailDebugLog('Connector Open Event FSM');
if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
this.emit('connection_started', this.iiot.opcuaClient, statusHandler);
internalDebugLog('Client Connected To ' + this.endpoint);
detailDebugLog('Client Options ' + JSON.stringify(this.iiot.opcuaClientOptions));
yield startSession('Open Event');
}
break;
case opcua_iiot_core_1.FsmConnectorStates.StateSessionRequested:
detailDebugLog('Connector Session Request Event FSM');
break;
case opcua_iiot_core_1.FsmConnectorStates.StateSessionActive:
detailDebugLog('Connector Session Active Event FSM');
if (!(0, underscore_1.isUndefined)(this.iiot))
this.iiot.sessionNodeRequests = 0;
this.emit('session_started', (_b = this.iiot) === null || _b === void 0 ? void 0 : _b.opcuaSession);
break;
case opcua_iiot_core_1.FsmConnectorStates.StateSessionRestart:
detailDebugLog('Connector Session Restart Event FSM');
this.emit('session_restart');
break;
case opcua_iiot_core_1.FsmConnectorStates.StateSessionClosed:
detailDebugLog('Connector Session Close Event FSM');
this.emit('session_closed');
if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
this.iiot.opcuaSession = undefined;
}
break;
case opcua_iiot_core_1.FsmConnectorStates.StateClosed:
detailDebugLog('Connector Client Close Event FSM');
this.emit('connection_closed');
if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
if (Object.keys(this.iiot.opcuaClient || {}).length > 1) {
(_c = this.iiot.opcuaClient) === null || _c === void 0 ? void 0 : _c.disconnect((err) => {
if (err) {
handleError(err);
}
});
this.iiot.opcuaClient = undefined;
}
}
break;
case opcua_iiot_core_1.FsmConnectorStates.StateLocked:
detailDebugLog('Connector Lock Event FSM');
break;
case opcua_iiot_core_1.FsmConnectorStates.StateUnlocked:
detailDebugLog('Connector Unlock Event FSM');
break;
case opcua_iiot_core_1.FsmConnectorStates.StateStopped:
detailDebugLog('Connector Stopped Event FSM');
this.emit('connection_stopped');
if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
resetAllTimer();
}
break;
case opcua_iiot_core_1.FsmConnectorStates.StateEnd:
detailDebugLog('Connector End Event FSM');
this.emit('connection_end');
if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
resetAllTimer();
}
break;
case opcua_iiot_core_1.FsmConnectorStates.StateReconfigured:
detailDebugLog('Connector Reconfigure Event FSM');
this.emit('connection_reconfigure');
if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
resetAllTimer();
}
break;
case opcua_iiot_core_1.FsmConnectorStates.StateRenewed:
detailDebugLog('Connector Renew Event FSM');
this.emit('connection_renew');
if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot)) {
resetAllTimer();
}
break;
default:
throw new Error('Connector FSM is not in a valid state');
}
});
this.iiot.stateMachine = opcua_iiot_core_connector_1.default.createConnectorFinalStateMachine();
this.iiot.stateService = opcua_iiot_core_connector_1.default.startConnectorMachineService(this.iiot.stateMachine);
this.iiot.stateSubscription = opcua_iiot_core_connector_1.default.subscribeConnectorFSMService(this.iiot.stateService, connectorStateEventFunction);
const resetAllTimer = () => {
detailDebugLog('Reset All Timer');
if (clientStartTimeout) {
clearTimeout(clientStartTimeout);
clientStartTimeout = null;
}
if (sessionStartTimeout) {
clearTimeout(sessionStartTimeout);
sessionStartTimeout = null;
}
if (disconnectTimeout) {
clearTimeout(disconnectTimeout);
disconnectTimeout = null;
}
};
/* --------------------- handle config node behaviour --------------------- */
this.iiot.registeredNodeList = {};
const renewFiniteStateMachine = () => {
if ((0, underscore_1.isUndefined)(this.iiot))
return;
this.iiot.stateMachine = null;
this.iiot.stateService = null;
this.iiot.stateSubscription = null;
// @xstate/fsm
this.iiot.stateMachine = opcua_iiot_core_connector_1.default.createConnectorFinalStateMachine();
this.iiot.stateService = opcua_iiot_core_connector_1.default.startConnectorMachineService(this.iiot.stateMachine);
this.iiot.stateSubscription = opcua_iiot_core_connector_1.default.subscribeConnectorFSMService(this.iiot.stateService, connectorStateEventFunction);
};
const registerForOPCUA = (opcuaNode, onAlias) => {
if (!opcuaNode) {
internalDebugLog('Node Not Valid To Register In Connector');
return;
}
internalDebugLog('Register In Connector NodeId: ' + opcuaNode.id);
if (!this.iiot || (0, underscore_1.isUndefined)(this.iiot.registeredNodeList)) {
internalDebugLog('Node Not Initialized With a registeredNodeList To Register In Connector');
return;
}
this.iiot.registeredNodeList[opcuaNode.id] = opcuaNode;
onAlias('opcua_client_not_ready', () => {
if ((0, opcua_iiot_core_1.isInitializedIIoTNode)(this.iiot) && this.iiot.stateService.state.value !== opcua_iiot_core_1.FsmConnectorStates.StateEnd) {
resetBadSession();
}
});
if (Object.keys(this.iiot.registeredNodeList).length === 1) {
internalDebugLog('Start Connector OPC UA Connection');
renewFiniteStateMachine();
this.iiot.stateService.send('INITOPCUA');
}
else {
}
};
const deregisterForOPCUA = (opcuaNode, done) => {
if (!opcuaNode) {
internalDebugLog('Node Not Valid To Deregister In Connector');
done();
return;
}
opcuaNode.removeAllListeners('opcua_client_not_ready');
if (!this.iiot || (0, underscore_1.isUndefined)(this.iiot.registeredNodeList)) {
internalDebugLog('Node Not Initialized With a registeredNodeList To Deregister In Connector');
return;
}
internalDebugLog('Deregister In Connector NodeId: ' + opcuaNode.id);
delete this.iiot.registeredNodeList[opcuaNode.id];
if (this.iiot.stateService.state.value === opcua_iiot_core_1.FsmConnectorStates.StateStopped || this.iiot.stateService.state.value === opcua_iiot_core_1.FsmConnectorStates.StateEnd) {
done();
return;
}
if (Object.keys(this.iiot.registeredNodeList).length === 0) {
//this.iiot.stateMachine.lock().stopopcua()
this.iiot.stateService.send('LOCK');
this.iiot.stateService.send('STOP');
if (this.iiot.opcuaClient) {
detailDebugLog('OPC UA Direct Disconnect On Unregister Of All Nodes');
try {
this.iiot.opcuaClient.disconnect((err) => {
if (err) {
handleError(err);
}
done();
});
}
catch (err) {
handleError(err);
done();
}
finally {
this.iiot.opcuaClient.removeAllListeners();
}
}
else {
done();
}
}
else {
done();
}
};
renewFiniteStateMachine();
this.functions = {
restartWithNewSettings,
registerForOPCUA,
deregisterForOPCUA,
getUpdatedServerOptions,
hasNoSession,
hasSession,
startSession
};
if (process.env.isTest == 'TRUE') {
this.functions = Object.assign(Object.assign({}, this.functions), { registerForOPCUA,
connectToClient,
connectOPCUAEndpoint,
resetBadSession,
renewConnection,
handleError,
deregisterForOPCUA });
}
}
try {
RED.nodes.registerType('OPCUA-IIoT-Connector', OPCUAIIoTConnectorConfiguration, {
credentials: {
user: { type: 'text' },
password: { type: 'password' }
}
});
}
catch (e) {
internalDebugLog(e.message);
}
/* --------------------- HTTP Requests --------------------- */
RED.httpAdmin.get('/opcuaIIoT/client/discover/:id/:discoveryUrl', RED.auth.needsPermission('opcua.discovery'), function (req, res) {
let node = RED.nodes.getNode(req.params.id);
let discoverUrlRequest = decodeURIComponent(req.params.discoveryUrl);
internalDebugLog('Get Discovery Request ' + JSON.stringify(req.params) + ' for ' + discoverUrlRequest);
if (node) {
if (discoverUrlRequest && !discoverUrlRequest.includes('opc.tcp://')) {
res.json([]);
}
else {
(0, node_opcua_1.findServers)(discoverUrlRequest, function (err, results) {
if (!err && results) {
const endpoints = results.servers.flatMap((server) => server.discoveryUrls);
res.json(endpoints);
}
else {
internalDebugLog('Perform Find Servers Request ' + err);
if (node.showErrors) {
node.error(err, { payload: '' });
}
res.json([]);
}
});
}
}
else {
internalDebugLog('Get Discovery Request None Node ' + JSON.stringify(req.params));
res.json([]);
}
});
RED.httpAdmin.get('/opcuaIIoT/client/endpoints/:id/:endpointUrl', RED.auth.needsPermission('opcua.endpoints'), function (req, res) {
var _a, _b;
let node = RED.nodes.getNode(req.params.id);
let endpointUrlRequest = decodeURIComponent(req.params.endpointUrl);
internalDebugLog('Get Endpoints Request ' + JSON.stringify(req.params) + ' for ' + endpointUrlRequest);
if ((0, underscore_1.isUndefined)(node.iiot)) {
node.error('IIoT invalid');
return;
}
if (node) {
if (endpointUrlRequest && !endpointUrlRequest.includes('opc.tcp://')) {
res.json([]);
}
else {
if (!((_a = node.iiot) === null || _a === void 0 ? void 0 : _a.opcuaClientOptions)) {
node.iiot.opcuaClientOptions = (_b = node.functions) === null || _b === void 0 ? void 0 : _b.getUpdatedServerOptions();
}
let discoveryClient = node_opcua_1.OPCUAClient.create(Object.assign(Object.assign({}, node.iiot.opcuaClientOptions), { endpointMustExist: false }));
discoveryClient.connect(endpointUrlRequest).then(() => {
internalDebugLog('Get Endpoints Connected For Request');
discoveryClient.getEndpoints(function (err, endpoints) {
if (err) {
if (node.showErrors) {
node.error(err, { payload: '' });
}
internalDebugLog('Get Endpoints Request Error ' + err);
res.json([]);
}
else {
internalDebugLog('Sending Endpoints For Request');
res.json(endpoints);
}
discoveryClient.disconnect(() => {
internalDebugLog('Get Endpoints Request Disconnect');
});
});
}).catch(function (err) {
internalDebugLog('Get Endpoints Request Error ' + err.message);
res.json([]);
});
}
}
else {
internalDebugLog('Get Endpoints Request None Node ' + JSON.stringify(req.params));
res.json([]);
}
});
RED.httpAdmin.get('/opcuaIIoT/plain/DataTypeIds', RED.auth.needsPermission('opcuaIIoT.plain.datatypes'), function (req, res) {
res.json(underscore_1.default.toArray(underscore_1.default.invert(node_opcua_1.DataTypeIds)));
});
RED.httpAdmin.get('/opcuaIIoT/plain/AttributeIds', RED.auth.needsPermission('opcuaIIoT.plain.attributeids'), function (req, res) {
res.json(underscore_1.default.toArray(underscore_1.default.invert(node_opcua_1.AttributeIds)));
});
RED.httpAdmin.get('/opcuaIIoT/plain/StatusCodes', RED.auth.needsPermission('opcuaIIoT.plain.statuscodes'), function (req, res) {
res.json(underscore_1.default.toArray(underscore_1.default.invert(node_opcua_1.StatusCodes)));
});
RED.httpAdmin.get('/opcuaIIoT/plain/ObjectTypeIds', RED.auth.needsPermission('opcuaIIoT.plain.objecttypeids'), function (req, res) {
res.json(node_opcua_1.ObjectTypeIds);
});
RED.httpAdmin.get('/opcuaIIoT/plain/VariableTypeIds', RED.auth.needsPermission('opcuaIIoT.plain.variabletypeids'), function (req, res) {
res.json(node_opcua_1.VariableTypeIds);
});
RED.httpAdmin.get('/opcuaIIoT/plain/ReferenceTypeIds', RED.auth.needsPermission('opcuaIIoT.plain.referencetypeids'), function (req, res) {
res.json(node_opcua_1.ReferenceTypeIds);
});
RED.httpAdmin.get('/opcuaIIoT/xmlsets/public', RED.auth.needsPermission('opcuaIIoT.xmlsets'), function (req, res) {
const xmlset = [
node_opcua_1.nodesets.di,
node_opcua_1.nodesets.adi,
'public/vendor/opc-foundation/xml/Opc.ISA95.NodeSet2.xml',
'public/vendor/opc-foundation/xml/Opc.Ua.Adi.NodeSet2.xml',
'public/vendor/opc-foundation/xml/Opc.Ua.Di.NodeSet2.xml',
'public/vendor/opc-foundation/xml/Opc.Ua.Gds.NodeSet2.xml',
'public/vendor/harting/10_di.xml',
'public/vendor/harting/20_autoid.xml',
'public/vendor/harting/30_aim.xml',
];
res.json(xmlset);
});
RED.httpAdmin.get('/opcuaIIoT/list/DataTypeIds', RED.auth.needsPermission('opcuaIIoT.list.datatypeids'), function (req, res) {
const resultTypeList = enumToTypeList(node_opcua_1.DataTypeIds);
res.json(resultTypeList);
});
RED.httpAdmin.get('/opcuaIIoT/list/EventTypeIds', RED.auth.needsPermission('opcuaIIoT.list.eventtypeids'), function (req, res) {
const eventTypesResults = enumToTypeList(node_opcua_1.ObjectTypeIds).filter((item) => {
return item.label.indexOf('Event') > -1;
});
res.json(eventTypesResults);
});
RED.httpAdmin.get('/opcuaIIoT/list/InstanceTypeIds', RED.auth.needsPermission('opcuaIIoT.list.instancetypeids'), function (req, res) {
const resultTypeList = [node_opcua_1.ObjectTypeIds, node_opcua_1.VariableTypeIds].flatMap((item) => enumToTypeList(item));
res.json(resultTypeList);
});
const enumToTypeList = (inputEnum) => {
return (0, helpers_1.getEnumKeys)(inputEnum).map((key) => {
return { nodeId: `i=${inputEnum[key]}`, label: key };
});
};
RED.httpAdmin.get('/opcuaIIoT/list/VariableTypeIds', RED.auth.needsPermission('opcuaIIoT.list.variabletypeids'), function (req, res) {
const resultTypeList = enumToTypeList(node_opcua_1.VariableTypeIds);
res.json(resultTypeList);
});
RED.httpAdmin.get('/opcuaIIoT/list/ReferenceTypeIds', RED.auth.needsPermission('opcuaIIoT.list.referencetypeids'), function (req, res) {
const resultTypeList = enumToTypeList(node_opcua_1.ReferenceTypeIds);
res.json(resultTypeList);
});
RED.httpAdmin.get('/opcuaIIoT/list/FilterTypes', RED.auth.needsPermission('opcuaIIoT.list.filterids'), function (req, res) {
const resultTypeList = [
{ name: 'dataType', label: 'Data Type' },
{ name: 'dataValue', label: 'Data Value' },
{ name: 'nodeClass', label: 'Node Class' },
{ name: 'typeDefinition', label: 'Type Definition' },
{ name: 'browseName', label: 'Browse Name' },
{ name: 'nodeId', label: 'Node Id' },
];
res.json(resultTypeList);
});
};
//# sourceMappingURL=opcua-iiot-connector.js.map