UNPKG

node-opcua-server

Version:

pure nodejs OPCUA SDK - module server

306 lines 16.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AddressSpaceAccessor = void 0; const chalk_1 = __importDefault(require("chalk")); const node_opcua_address_space_1 = require("node-opcua-address-space"); const node_opcua_assert_1 = __importDefault(require("node-opcua-assert")); const node_opcua_data_value_1 = require("node-opcua-data-value"); const node_opcua_date_time_1 = require("node-opcua-date-time"); const node_opcua_nodeid_1 = require("node-opcua-nodeid"); const node_opcua_pki_1 = require("node-opcua-pki"); const node_opcua_status_code_1 = require("node-opcua-status-code"); const node_opcua_types_1 = require("node-opcua-types"); const node_opcua_variant_1 = require("node-opcua-variant"); function checkReadProcessedDetails(historyReadDetails) { if (!historyReadDetails.aggregateConfiguration) { historyReadDetails.aggregateConfiguration = new node_opcua_types_1.AggregateConfiguration({ useServerCapabilitiesDefaults: true }); } if (historyReadDetails.aggregateConfiguration.useServerCapabilitiesDefaults) { return node_opcua_status_code_1.StatusCodes.Good; } // The PercentDataGood and PercentDataBad shall follow the following relationship // PercentDataGood ≥ (100 – PercentDataBad). // If they are equal the result of the PercentDataGood calculation is used. // If the values entered for PercentDataGood and PercentDataBad do not result in a valid calculation // (e.g. Bad = 80; Good = 0) the result will have a StatusCode of Bad_AggregateInvalidInputs. if (historyReadDetails.aggregateConfiguration.percentDataGood < 100 - historyReadDetails.aggregateConfiguration.percentDataBad) { return node_opcua_status_code_1.StatusCodes.BadAggregateInvalidInputs; } // The StatusCode Bad_AggregateInvalidInputs will be returned if the value of PercentDataGood // or PercentDataBad exceed 100. if (historyReadDetails.aggregateConfiguration.percentDataGood > 100 || historyReadDetails.aggregateConfiguration.percentDataGood < 0) { return node_opcua_status_code_1.StatusCodes.BadAggregateInvalidInputs; } if (historyReadDetails.aggregateConfiguration.percentDataBad > 100 || historyReadDetails.aggregateConfiguration.percentDataBad < 0) { return node_opcua_status_code_1.StatusCodes.BadAggregateInvalidInputs; } return node_opcua_status_code_1.StatusCodes.Good; } class AddressSpaceAccessor { constructor(addressSpace) { this.addressSpace = addressSpace; } async browse(context, nodesToBrowse) { const results = []; for (const browseDescription of nodesToBrowse) { results.push(await this.browseNode(browseDescription, context)); (0, node_opcua_assert_1.default)(browseDescription.nodeId, "expecting a nodeId"); } return results; } async read(context, readRequest) { /** * * * @param {number} maxAge: Maximum age of the value to be read in milliseconds. * * The age of the value is based on the difference between * the ServerTimestamp and the time when the Server starts processing the request. For example if the Client * specifies a maxAge of 500 milliseconds and it takes 100 milliseconds until the Server starts processing * the request, the age of the returned value could be 600 milliseconds prior to the time it was requested. * If the Server has one or more values of an Attribute that are within the maximum age, it can return any one * of the values or it can read a new value from the data source. The number of values of an Attribute that * a Server has depends on the number of MonitoredItems that are defined for the Attribute. In any case, * the Client can make no assumption about which copy of the data will be returned. * If the Server does not have a value that is within the maximum age, it shall attempt to read a new value * from the data source. * If the Server cannot meet the requested maxAge, it returns its 'best effort' value rather than rejecting the * request. * This may occur when the time it takes the Server to process and return the new data value after it has been * accessed is greater than the specified maximum age. * If maxAge is set to 0, the Server shall attempt to read a new value from the data source. * If maxAge is set to the max Int32 value or greater, the Server shall attempt to get a cached value. * Negative values are invalid for maxAge. */ readRequest.maxAge = readRequest.maxAge || 0; const timestampsToReturn = readRequest.timestampsToReturn; const nodesToRead = readRequest.nodesToRead || []; context.currentTime = (0, node_opcua_date_time_1.getCurrentClock)(); const dataValues = []; for (const readValueId of nodesToRead) { const dataValue = await this.readNode(context, readValueId, readRequest.maxAge, timestampsToReturn); dataValues.push(dataValue); } return dataValues; } async write(context, nodesToWrite) { context.currentTime = (0, node_opcua_date_time_1.getCurrentClock)(); await (0, node_opcua_address_space_1.ensureDatatypeExtracted)(this.addressSpace); const results = []; for (const writeValue of nodesToWrite) { const statusCode = await this.writeNode(context, writeValue); results.push(statusCode); } return results; } async call(context, methodsToCall) { const results = []; await (0, node_opcua_address_space_1.ensureDatatypeExtracted)(this.addressSpace); for (const methodToCall of methodsToCall) { const result = await this.callMethod(context, methodToCall); results.push(result); } return results; } async historyRead(context, historyReadRequest) { (0, node_opcua_assert_1.default)(context instanceof node_opcua_address_space_1.SessionContext); (0, node_opcua_assert_1.default)(historyReadRequest instanceof node_opcua_types_1.HistoryReadRequest); const timestampsToReturn = historyReadRequest.timestampsToReturn; const historyReadDetails = historyReadRequest.historyReadDetails; const releaseContinuationPoints = historyReadRequest.releaseContinuationPoints; (0, node_opcua_assert_1.default)(historyReadDetails instanceof node_opcua_types_1.HistoryReadDetails); // ReadAnnotationDataDetails | ReadAtTimeDetails | ReadEventDetails | ReadProcessedDetails | ReadRawModifiedDetails; const nodesToRead = historyReadRequest.nodesToRead || []; (0, node_opcua_assert_1.default)(Array.isArray(nodesToRead)); const _q = async (m) => { const continuationPoint = m.nodeToRead.continuationPoint; return await this.historyReadNode(context, m.nodeToRead, m.processDetail, timestampsToReturn, { continuationPoint, releaseContinuationPoints }); }; if (historyReadDetails instanceof node_opcua_types_1.ReadProcessedDetails) { // if (!historyReadDetails.aggregateType || historyReadDetails.aggregateType.length !== nodesToRead.length) { return [new node_opcua_types_1.HistoryReadResult({ statusCode: node_opcua_status_code_1.StatusCodes.BadInvalidArgument })]; } const parameterStatus = checkReadProcessedDetails(historyReadDetails); if (parameterStatus !== node_opcua_status_code_1.StatusCodes.Good) { return [new node_opcua_types_1.HistoryReadResult({ statusCode: parameterStatus })]; } const promises = []; let index = 0; for (const nodeToRead of nodesToRead) { const aggregateType = historyReadDetails.aggregateType[index]; const processDetail = new node_opcua_types_1.ReadProcessedDetails({ ...historyReadDetails, aggregateType: [aggregateType] }); promises.push(_q({ nodeToRead, processDetail, index })); index++; } const results = await Promise.all(promises); return results; } const _r = async (nodeToRead, index) => { const continuationPoint = nodeToRead.continuationPoint; return await this.historyReadNode(context, nodeToRead, historyReadDetails, timestampsToReturn, { continuationPoint, releaseContinuationPoints, }); }; const promises = []; let index = 0; for (const nodeToRead of nodesToRead) { promises.push(_r(nodeToRead, index)); index++; } const result = await Promise.all(promises); return result; } async browseNode(browseDescription, context) { if (!this.addressSpace) { throw new Error("Address Space has not been initialized"); } const nodeId = (0, node_opcua_nodeid_1.resolveNodeId)(browseDescription.nodeId); const r = this.addressSpace.browseSingleNode(nodeId, browseDescription instanceof node_opcua_types_1.BrowseDescription ? browseDescription : new node_opcua_types_1.BrowseDescription({ ...browseDescription, nodeId }), context); return r; } async readNode(context, nodeToRead, maxAge, timestampsToReturn) { (0, node_opcua_assert_1.default)(context instanceof node_opcua_address_space_1.SessionContext); const nodeId = (0, node_opcua_nodeid_1.resolveNodeId)(nodeToRead.nodeId); const attributeId = nodeToRead.attributeId; const indexRange = nodeToRead.indexRange; const dataEncoding = nodeToRead.dataEncoding; if (timestampsToReturn === node_opcua_data_value_1.TimestampsToReturn.Invalid) { return new node_opcua_data_value_1.DataValue({ statusCode: node_opcua_status_code_1.StatusCodes.BadTimestampsToReturnInvalid }); } timestampsToReturn = (0, node_opcua_data_value_1.coerceTimestampsToReturn)(timestampsToReturn); const obj = this.__findNode((0, node_opcua_nodeid_1.coerceNodeId)(nodeId)); let dataValue; if (!obj) { // Object Not Found return new node_opcua_data_value_1.DataValue({ statusCode: node_opcua_status_code_1.StatusCodes.BadNodeIdUnknown }); } else { // check access // BadUserAccessDenied // BadNotReadable // invalid attributes : BadNodeAttributesInvalid // invalid range : BadIndexRangeInvalid dataValue = obj.readAttribute(context, attributeId, indexRange, dataEncoding); dataValue = (0, node_opcua_data_value_1.apply_timestamps_no_copy)(dataValue, timestampsToReturn, attributeId); if (timestampsToReturn === node_opcua_data_value_1.TimestampsToReturn.Server) { dataValue.sourceTimestamp = null; dataValue.sourcePicoseconds = 0; } if ((timestampsToReturn === node_opcua_data_value_1.TimestampsToReturn.Both || timestampsToReturn === node_opcua_data_value_1.TimestampsToReturn.Server) && (!dataValue.serverTimestamp || (0, node_opcua_date_time_1.isMinDate)(dataValue.serverTimestamp))) { const t = context.currentTime ? context.currentTime.timestamp : (0, node_opcua_date_time_1.getCurrentClock)().timestamp; dataValue.serverTimestamp = t; dataValue.serverPicoseconds = 0; // context.currentTime.picoseconds; } return dataValue; } } __findNode(nodeId) { const namespaceIndex = nodeId.namespace || 0; if (namespaceIndex && namespaceIndex >= (this.addressSpace?.getNamespaceArray().length || 0)) { return null; } const namespace = this.addressSpace.getNamespace(namespaceIndex); return namespace.findNode2(nodeId); } async writeNode(context, writeValue) { await (0, node_opcua_address_space_1.resolveOpaqueOnAddressSpace)(this.addressSpace, writeValue.value.value); (0, node_opcua_assert_1.default)(context instanceof node_opcua_address_space_1.SessionContext); (0, node_opcua_assert_1.default)(writeValue.schema.name === "WriteValue"); (0, node_opcua_assert_1.default)(writeValue.value instanceof node_opcua_data_value_1.DataValue); if (!writeValue.value.value) { /* missing Variant */ return node_opcua_status_code_1.StatusCodes.BadTypeMismatch; } (0, node_opcua_assert_1.default)(writeValue.value.value instanceof node_opcua_variant_1.Variant); const nodeId = writeValue.nodeId; const obj = this.__findNode(nodeId); if (!obj) { return node_opcua_status_code_1.StatusCodes.BadNodeIdUnknown; } else { return await new Promise((resolve, reject) => { obj.writeAttribute(context, writeValue, (err, statusCode) => { if (err) { reject(err); } else { resolve(statusCode); } }); }); } } async callMethod(context, methodToCall) { return await (0, node_opcua_address_space_1.callMethodHelper)(context, this.addressSpace, methodToCall); } async historyReadNode(context, nodeToRead, historyReadDetails, timestampsToReturn, continuationData) { (0, node_opcua_assert_1.default)(context instanceof node_opcua_address_space_1.SessionContext); if (timestampsToReturn === node_opcua_data_value_1.TimestampsToReturn.Invalid) { return new node_opcua_types_1.HistoryReadResult({ statusCode: node_opcua_status_code_1.StatusCodes.BadTimestampsToReturnInvalid }); } const nodeId = nodeToRead.nodeId; const indexRange = nodeToRead.indexRange; const dataEncoding = nodeToRead.dataEncoding; const continuationPoint = nodeToRead.continuationPoint; timestampsToReturn = (0, node_opcua_data_value_1.coerceTimestampsToReturn)(timestampsToReturn); if (timestampsToReturn === node_opcua_data_value_1.TimestampsToReturn.Invalid) { return new node_opcua_types_1.HistoryReadResult({ statusCode: node_opcua_status_code_1.StatusCodes.BadTimestampsToReturnInvalid }); } const obj = this.__findNode(nodeId); if (!obj) { // may be return BadNodeIdUnknown in dataValue instead ? // Object Not Found return new node_opcua_types_1.HistoryReadResult({ statusCode: node_opcua_status_code_1.StatusCodes.BadNodeIdUnknown }); } else { // istanbul ignore next if (!obj.historyRead) { // note : Object and View may also support historyRead to provide Event historical data // todo implement historyRead for Object and View const msg = " this node doesn't provide historyRead! probably not a UAVariable\n " + obj.nodeId.toString() + " " + obj.browseName.toString() + "\n" + "with " + nodeToRead.toString() + "\n" + "HistoryReadDetails " + historyReadDetails.toString(); // istanbul ignore next if (node_opcua_pki_1.doDebug) { (0, node_opcua_pki_1.debugLog)(chalk_1.default.cyan("ServerEngine#_historyReadNode "), chalk_1.default.white.bold(msg)); } throw new Error(msg); } // check access // BadUserAccessDenied // BadNotReadable // invalid attributes : BadNodeAttributesInvalid // invalid range : BadIndexRangeInvalid const result = await obj.historyRead(context, historyReadDetails, indexRange, dataEncoding, continuationData); (0, node_opcua_assert_1.default)(result.isValid()); return result; } } } exports.AddressSpaceAccessor = AddressSpaceAccessor; //# sourceMappingURL=addressSpace_accessor.js.map