node-opcua-server
Version:
pure nodejs OPCUA SDK - module server
306 lines • 16.8 kB
JavaScript
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
;