UNPKG

node-red-contrib-iiot-opcua

Version:

An Industrial IoT OPC UA toolbox contribution package for Node-RED based on node-opcua.

307 lines (304 loc) 11.9 kB
/** 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 __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const opcua_iiot_core_1 = require("./opcua-iiot-core"); const node_opcua_1 = require("node-opcua"); const debug_1 = __importDefault(require("debug")); const underscore_1 = require("underscore"); const internalDebugLog = (0, debug_1.default)('opcuaIIoT:browser'); // eslint-disable-line no-use-before-define const detailDebugLog = (0, debug_1.default)('opcuaIIoT:browser:details'); // eslint-disable-line no-use-before-define const crawlerInternalDebugLog = (0, debug_1.default)('opcuaIIoT:browser:crawler'); // eslint-disable-line no-use-before-define const crawlerDetailDebugLog = (0, debug_1.default)('opcuaIIoT:browser:crawler:details'); // eslint-disable-line no-use-before-define const browse = (session, nodeIdToBrowse) => { return new Promise(function (resolve, reject) { let browseOptions = [ { nodeId: nodeIdToBrowse, referenceTypeId: 'Organizes', includeSubtypes: true, browseDirection: node_opcua_1.BrowseDirection.Forward, resultMask: 63 }, { nodeId: nodeIdToBrowse, referenceTypeId: 'Aggregates', includeSubtypes: true, browseDirection: node_opcua_1.BrowseDirection.Forward, resultMask: 63 } ]; session.browse(browseOptions, (err, browseResult) => { if (err) { reject(err); } else { if ((0, underscore_1.isUndefined)(browseResult)) { reject('Browse Results are Undefined'); } else { resolve(browseResult); } } }); }); }; const browseAddressSpaceItems = function (session, addressSpaceItems) { return new Promise(function (resolve, reject) { let browseOptions = []; addressSpaceItems.flatMap(function (item) { browseOptions.push({ nodeId: item.nodeId, referenceTypeId: 'Organizes', includeSubtypes: true, browseDirection: node_opcua_1.BrowseDirection.Forward, resultMask: 63 }); browseOptions.push({ nodeId: item.nodeId, referenceTypeId: 'Aggregates', includeSubtypes: true, browseDirection: node_opcua_1.BrowseDirection.Forward, resultMask: 63 }); }); if (browseOptions.length === 0) { resolve([]); return; } session.browse(browseOptions, (err, browseResult) => { if (err) { reject(err); } else { if ((0, underscore_1.isUndefined)(browseResult)) { reject('Browse Results are Undefined'); } else { resolve(browseResult); } } }); }); }; const createCrawler = function (session) { return new node_opcua_1.NodeCrawler(session); }; const crawl = (session, nodeIdToCrawl, msg, sendWrapper) => { if (!nodeIdToCrawl) { return new Error('NodeId To Crawl Not Valid'); } const message = Object.assign({}, msg); const crawler = createCrawler(session); let crawlerResult = []; const data = { onBrowse: function (crawler, cacheNode) { crawlerResult.push(cacheNode); node_opcua_1.NodeCrawler.follow(crawler, cacheNode, this); } }; try { const crawlback = (err) => { if (err) { sendWrapper(err); } else { sendWrapper({ crawlerResult, rootNodeId: nodeIdToCrawl }); } }; const readCallback = (err, response) => { if (err) { sendWrapper(err); } else if (response && response.some((res) => { var _a; return ((_a = res.statusCode) === null || _a === void 0 ? void 0 : _a.name) === 'BadNodeIdUnknown'; })) { sendWrapper(new Error('NodeId Not Valid: Please enter a valid NodeId, under the "OPC UA Nodes" tab of the Inject Node configuration.')); } else { crawler.crawl(nodeIdToCrawl, data, crawlback); } }; verifyNodeExists(session, nodeIdToCrawl, readCallback); } catch (err) { return err; } }; /** * Crawl based on addressSpaceItems from an inject node. * * This seems needlessly overcomplicated, but that is necessary to avoid UnhandledPromiseRejection errors from the crawl function. * */ const crawlAddressSpaceItems = (session, payload, sendWrapper, timeout) => { const crawler = createCrawler(session); const crawlerPromises = []; const resolvers = []; payload.addressSpaceItems.forEach((item, index) => { if (!item.nodeId) { coreBrowser.internalDebugLog('Item Not To Crawl - Missing NodeId'); return; } // Each item should track results itself // results will be combined in the payload.value field, but remain independent in payload.crawlerResult let crawlerResult = []; const data = { onBrowse: (crawler, cacheNode, userData) => { if (!cacheNode) { coreBrowser.internalDebugLog('Item Not To Crawl - Missing NodeId'); } crawlerResult.push(cacheNode); node_opcua_1.NodeCrawler.follow(crawler, cacheNode, userData); } }; /** * Handle the response of the verifyNodeExists function. * If the response doesn't contain any error, then the node exists and it can be crawled.. */ const readCallback = (err, response) => { crawlerPromises.push(new Promise((resolve, reject) => { resolvers.push({ resolve, reject }); setTimeout(reject, timeout * 1000, new Error('Timeout')); }).catch((test) => test)); // The catch needs to be here, despite seeming useless /** * Resolves the promise of the current item. * If the current item is the last, wait for all promises to resolve, then call the send function. * Intended as a callback for the crawl function, but also called directly, since this needs to be called every time. */ const crawlback = (err) => { resolvers[index].resolve(crawlerResult); // Ensure only one message is sent if (index === payload.addressSpaceItems.length - 1) { Promise.allSettled(crawlerPromises).then((promiseList) => { sendWrapper({ rootNodeId: item.nodeId, payload, crawlerResult: promiseList, promises: true }); }).catch((err) => { sendWrapper(err); }); } }; // Crawlback must be called in every branch to ensure a message is sent if (err) { resolvers[index].reject(err); crawlback(err); return; } else if (response && response.some((res) => { var _a; return ((_a = res.statusCode) === null || _a === void 0 ? void 0 : _a.name) === 'BadNodeIdUnknown'; })) { const error = new Error('NodeId Not Valid: Please enter a valid NodeId, under the "OPC UA Nodes" tab of the Inject Node configuration.'); resolvers[index].reject(error); crawlback(error); return; } crawler.crawl(item.nodeId, data, crawlback); }; verifyNodeExists(session, item.nodeId, readCallback); }); }; const browseToRoot = function () { detailDebugLog('Browse To Root ' + opcua_iiot_core_1.OBJECTS_ROOT); return opcua_iiot_core_1.OBJECTS_ROOT; }; const extractNodeIdFromTopic = function (payload, node) { let rootNodeId = null; if (payload.actiontype === 'browse') { // event driven browsing if (payload.root && payload.root.nodeId) { internalDebugLog('Root Selected External ' + payload.root); rootNodeId = payload.root.nodeId; } else { rootNodeId = node.nodeId; } detailDebugLog('Extracted NodeId ' + rootNodeId); rootNodeId = rootNodeId || browseToRoot(); } return rootNodeId; }; const transformToEntry = (reference) => { if (reference) { try { return reference.toJSON(); } catch (err) { internalDebugLog(err); if (reference.referenceTypeId) { return { referenceTypeId: reference.referenceTypeId.toString(), isForward: reference.isForward, nodeId: reference.nodeId.toString(), browseName: reference.browseName.toString(), displayName: reference.displayName.toString(), nodeClass: reference.nodeClass.toString(), typeDefinition: reference.typeDefinition.toString() }; } } } else { internalDebugLog('Empty Reference On Browse'); } return reference; }; const initBrowserNode = function () { return { browseTopic: opcua_iiot_core_1.OBJECTS_ROOT, iiot: Object.assign(Object.assign({}, (0, opcua_iiot_core_1.initCoreNode)()), { items: [], messageList: [], delayMessageTimer: [] }) }; }; const browseErrorHandling = function (node, err, msg, lists, errorHandler, statusHandler, oldStatusParameter = undefined, showErrors = true, showStatusActivities = true) { let results = (lists === null || lists === void 0 ? void 0 : lists.browserResults) || []; if (err) { internalDebugLog(typeof node + 'Error ' + err); if (showErrors) { errorHandler(err, msg); } if ((0, opcua_iiot_core_1.isSessionBad)(err)) { node.emit('opcua_client_not_ready'); } } else { internalDebugLog(typeof node + ' Done With Error'); if (results.length) { detailDebugLog(results.length + 'items in lists of browser results'); } } if (showStatusActivities && oldStatusParameter) { node.oldStatusParameter = (0, opcua_iiot_core_1.setNodeStatusTo)(node, 'error', oldStatusParameter, showStatusActivities, statusHandler); } }; /** * Verifies that a node exists and catches the error, sending a nonexistent node to the crawler causes an error * * The endCallback function should do error checking and then call the crawl function */ const verifyNodeExists = (session, nodeId, endCallback) => { session.read([{ nodeId: nodeId }], endCallback); }; const coreBrowser = { internalDebugLog, detailDebugLog, crawlerInternalDebugLog, crawlerDetailDebugLog, // Browser functions browse, browseAddressSpaceItems, createCrawler, crawl, crawlAddressSpaceItems, browseToRoot, extractNodeIdFromTopic, transformToEntry, initBrowserNode, browseErrorHandling, }; exports.default = coreBrowser; //# sourceMappingURL=opcua-iiot-core-browser.js.map