Skip to content

Commit

Permalink
first implementation of Min and Max aggregate on server side
Browse files Browse the repository at this point in the history
  • Loading branch information
erossignon committed May 30, 2020
1 parent 7e4bed0 commit 69ebb4f
Show file tree
Hide file tree
Showing 9 changed files with 320 additions and 184 deletions.
45 changes: 38 additions & 7 deletions packages/node-opcua-address-space/src/address_space_private.ts
@@ -1,25 +1,46 @@
/**
* @module node-opcua-address-space.Private
*/
import { ExtraDataTypeManager } from "node-opcua-client-dynamic-extension-object";
import { NodeClass } from "node-opcua-data-model";
import { ExtensionObject } from "node-opcua-extension-object";
import { NodeId, NodeIdLike } from "node-opcua-nodeid";
import { BrowsePath, ModelChangeStructureDataType } from "node-opcua-types";
import {
ExtraDataTypeManager
} from "node-opcua-client-dynamic-extension-object";
import {
NodeClass, QualifiedNameLike
} from "node-opcua-data-model";
import {
ExtensionObject
} from "node-opcua-extension-object";
import {
NodeId, NodeIdLike
} from "node-opcua-nodeid";
import {
BrowsePath, ModelChangeStructureDataType, ReadProcessedDetails, HistoryReadResult
} from "node-opcua-types";
import {
ConstructorFuncWithSchema
} from "node-opcua-factory";
import {
NumericRange
} from "node-opcua-numeric-range";
import {
CallbackT
} from "node-opcua-status-code";

import {
AddReferenceOpts,
AddressSpace,
BaseNode as BaseNodePublic,
UADataType,
UAView
UAView,
SessionContext,
ContinuationPoint
} from "../source";
import { NamespacePrivate } from "./namespace_private";
import { Reference } from "./reference";
import { UAObjectType } from "./ua_object_type";
import { UAVariableType } from "./ua_variable_type";
import { ConstructorFuncWithSchema } from "node-opcua-factory/source";
import { ExtensionObjectConstructorFuncWithSchema } from "./ua_data_type";
import { UAVariable } from "./ua_variable";

export interface AddressSpacePrivate extends AddressSpace {

Expand Down Expand Up @@ -77,4 +98,14 @@ export interface AddressSpacePrivate extends AddressSpace {

getDataTypeManager(): ExtraDataTypeManager;

///
_readProcessedDetails?: (variable: UAVariable,
context: SessionContext,
historyReadDetails: ReadProcessedDetails,
indexRange: NumericRange | null,
dataEncoding: QualifiedNameLike | null,
continuationPoint: ContinuationPoint | null,
callback: CallbackT<HistoryReadResult>
) => void;

}
Expand Up @@ -19,6 +19,9 @@ import {
} from "node-opcua-service-history";
import { StatusCodes } from "node-opcua-status-code";
import { CallbackT } from "node-opcua-status-code";
import { ObjectIds } from "node-opcua-constants";
import { NodeId } from "node-opcua-nodeid";


import { DataType } from "node-opcua-variant";
import {
Expand All @@ -31,7 +34,6 @@ import {
import { AddressSpace } from "../address_space";
import { SessionContext } from "../session_context";
import { UAVariable } from "../ua_variable";

// tslint:disable:no-var-requires
const Dequeue = require("dequeue");

Expand Down Expand Up @@ -654,61 +656,19 @@ function _historyRead(

} else if (historyReadDetails instanceof ReadProcessedDetails) {

// OPC Unified Architecture, Part 11 27 Release 1.03
//
// This structure is used to compute Aggregate values, qualities, and timestamps from data in
// the history database for the specified time domain for one or more HistoricalDataNodes. The
// time domain is divided into intervals of duration ProcessingInterval. The specified Aggregate
// Type is calculated for each interval beginning with startTime by using the data within the next
// ProcessingInterval.
// For example, this function can provide hourly statistics such as Maximum, Minimum , and
// Average for each item during the specified time domain when ProcessingInterval is 1 hour.
// The domain of the request is defined by startTime, endTime, and ProcessingInterval. All three
// shall be specified. If endTime is less than startTime then the data shall be returned in reverse
// order with the later data coming first. If startTime and endTime are the same then the Server
// shall return Bad_InvalidArgument as there is no meaningful way to interpret such a case. If
// the ProcessingInterval is specified as 0 then Aggregates shall be calculated using one interval
// starting at startTime and ending at endTime.
// The aggregateType[] parameter allows a Client to request multiple Aggregate calculations per
// requested NodeId. If multiple Aggregates are requested then a corresponding number of
// entries are required in the NodesToRead array.
// For example, to request Min Aggregate for NodeId FIC101, FIC102, and both Min and Max
// Aggregates for NodeId FIC103 would require NodeId FIC103 to appear twice in the
// NodesToRead array request parameter.
// aggregateType[] NodesToRead[]
// Min FIC101
// Min FIC102
// Min FIC103
// Max FIC103
// If the array of Aggregates does not match the array of NodesToRead then the Server shall
// return a StatusCode of Bad_AggregateListMismatch.
// The aggregateConfiguration parameter allows a Client to override the Aggregate configuration
// settings supplied by the AggregateConfiguration Object on a per call basis. See Part 13 for
// more information on Aggregate configurations. If the Server does not support the ability to
// override the Aggregate configuration settings then it shall return a StatusCode of Bad_
// AggregateConfigurationRejected. If the Aggregate is not valid for the Node then the
// StatusCode shall be Bad_AggregateNotSupported.
// The values used in computing the Aggregate for each interval shall include any value that
// falls exactly on the timestamp at the beginning of the interval, but shall not include any value
// that falls directly on the timestamp ending the interval. Thus, each value shall be included
// only once in the calculation. If the time domain is in reverse order then we consider the later
// timestamp to be the one beginning the sub interval, and the earlier timestamp to be the one
// ending it. Note that this means that simply swapping the start and end times will not result in
// getting the same values back in reverse order as the intervals being requested in the two
// cases are not the same.
// If an Aggregate is taking a long time to calculate then the Server can return partial results
// with a continuation point. This might be done if the calculation is going to take more time th an
// the Client timeout hint. In some cases it may take longer than the Client timeout hint to
// calculate even one Aggregate result. Then the Server may return zero results with a
// continuation point that allows the Server to resume the calculation on the next Client read
// call.
const addressSpace = this.addressSpace;
if (!addressSpace._readProcessedDetails) {
const result = new HistoryReadResult({
historyData: new HistoryData({}),
statusCode: StatusCodes.BadHistoryOperationUnsupported
});
return callback(null, result);

// todo provide correct implementation
const result = new HistoryReadResult({
historyData: new HistoryData({ dataValues: [] }),
statusCode: StatusCodes.BadHistoryOperationUnsupported
});
return callback(null, result);
} else {
return addressSpace._readProcessedDetails(
this, context, historyReadDetails, indexRange,
dataEncoding, continuationPoint, callback);
}

} else if (historyReadDetails instanceof ReadAtTimeDetails) {

Expand Down
4 changes: 3 additions & 1 deletion packages/node-opcua-aggregates/package.json
Expand Up @@ -12,8 +12,10 @@
"dependencies": {
"@types/async": "^3.2.2",
"node-opcua-address-space": "^2.6.0-alpha.7",
"node-opcua-numeric-range": "^2.6.0-alpha.7",
"node-opcua-constants": "^2.6.0-alpha.1",
"node-opcua-data-value": "^2.6.0-alpha.7",
"node-opcua-data-model": "^2.6.0-alpha.7",
"node-opcua-nodeid": "^2.6.0-alpha.1",
"node-opcua-nodesets": "^2.6.0-alpha.1",
"node-opcua-service-history": "^2.6.0-alpha.7",
Expand Down Expand Up @@ -50,4 +52,4 @@
],
"homepage": "http://node-opcua.github.io/",
"gitHead": "07dcdd8e8c7f2b55544c6e23023093e35674829c"
}
}
92 changes: 48 additions & 44 deletions packages/node-opcua-aggregates/source/aggregates.ts
Expand Up @@ -8,6 +8,8 @@ import { DataType } from "node-opcua-variant";

import { AddressSpace, BaseNode, UAObject, UAServerCapabilities, UAVariable } from "node-opcua-address-space";
import { AggregateConfigurationOptionsEx } from "./interval";
import { AddressSpacePrivate } from "node-opcua-address-space/src/address_space_private";
import { readProcessedDetails } from "./read_processed_details";
// import { HistoryServerCapabilities } from "node-opcua-server";

/*
Expand Down Expand Up @@ -44,8 +46,8 @@ const historicalCapabilitiesDefaultProperties /*: HistoryServerCapabilities */ =
};

export function createHistoryServerCapabilities(
addressSpace: AddressSpace,
serverCapabilities: UAServerCapabilities
addressSpace: AddressSpace,
serverCapabilities: UAServerCapabilities
): UAObject {

/* istanbul ignore next */
Expand All @@ -66,8 +68,8 @@ export function createHistoryServerCapabilities(
}

function setHistoricalServerCapabilities(
historyServerCapabilities: any,
defaultProperties: any
historyServerCapabilities: any,
defaultProperties: any
) {
function setBoolean(propName: string) {
const lowerCase = utils.lowerFirstLetter(propName);
Expand Down Expand Up @@ -120,45 +122,45 @@ function setHistoricalServerCapabilities(
}

export type AggregateFunctionName =
"AnnotationCount" |
"Average" |
"Count" |
"Delta" |
"DeltaBounds" |
"DurationBad" |
"DurationGood" |
"DurationInStateNonZero" |
"DurationInStateZero" |
"EndBound" |
"Interpolative" |
"Maximum" |
"Maximum2" |
"MaximumActualTime" |
"MaximumActualTime2" |
"Minimum" |
"Minimum2" |
"MinimumActualTime" |
"MinimumActualTime2" |
"NumberOfTransitions" |
"PercentBad" |
"PercentGood" |
"Range" |
"Range2" |
"StandardDeviationPopulation" |
"StandardDeviationSample" |
"Start" |
"StartBound" |
"TimeAverage" |
"TimeAverage2" |
"Total" |
"Total2" |
"VariancePopulation" |
"VarianceSample" |
"WorstQuality" |
"WorstQuality2";
"AnnotationCount" |
"Average" |
"Count" |
"Delta" |
"DeltaBounds" |
"DurationBad" |
"DurationGood" |
"DurationInStateNonZero" |
"DurationInStateZero" |
"EndBound" |
"Interpolative" |
"Maximum" |
"Maximum2" |
"MaximumActualTime" |
"MaximumActualTime2" |
"Minimum" |
"Minimum2" |
"MinimumActualTime" |
"MinimumActualTime2" |
"NumberOfTransitions" |
"PercentBad" |
"PercentGood" |
"Range" |
"Range2" |
"StandardDeviationPopulation" |
"StandardDeviationSample" |
"Start" |
"StartBound" |
"TimeAverage" |
"TimeAverage2" |
"Total" |
"Total2" |
"VariancePopulation" |
"VarianceSample" |
"WorstQuality" |
"WorstQuality2";

function addAggregateFunctionSupport(
addressSpace: AddressSpace, functionName: number): void {
addressSpace: AddressSpace, functionName: number): void {

/* istanbul ignore next */
if (!functionName) {
Expand Down Expand Up @@ -194,7 +196,6 @@ function addAggregateFunctionSupport(
});
}


export enum AggregateFunction {
AnnotationCount = ObjectIds.AggregateFunction_AnnotationCount,
Average = ObjectIds.AggregateFunction_Average,
Expand Down Expand Up @@ -273,11 +274,14 @@ export function addAggregateSupport(addressSpace: AddressSpace) {
addAggregateFunctionSupport(addressSpace, AggregateFunction.Minimum);
addAggregateFunctionSupport(addressSpace, AggregateFunction.Maximum);

const addressSpaceInternal = addressSpace as AddressSpacePrivate;
addressSpaceInternal._readProcessedDetails = readProcessedDetails;

}

export function installAggregateConfigurationOptions(
node: UAVariable,
options: AggregateConfigurationOptionsEx
node: UAVariable,
options: AggregateConfigurationOptionsEx
) {
const nodePriv = node as any;
const aggregateConfiguration = nodePriv.$historicalDataConfiguration.aggregateConfiguration;
Expand Down
6 changes: 3 additions & 3 deletions packages/node-opcua-aggregates/source/minmax.ts
Expand Up @@ -100,7 +100,7 @@ function calculateIntervalMinOrMaxValue(
continue;
}
const compare = predicate(selectedValue, dataValue.value);
if (compare === "equal") {
if (compare === "equal") {
counter = 1;
continue;
}
Expand Down Expand Up @@ -145,13 +145,13 @@ function calculateIntervalMinOrMaxValue(
export function calculateIntervalMinValue(interval: Interval, options: AggregateConfigurationOptions): DataValue {
return calculateIntervalMinOrMaxValue(interval, options,
(a: Variant, b: Variant) =>
a.value > b.value ? "select" : (a.value === b.value ? "equal" : "reject") );
a.value > b.value ? "select" : (a.value === b.value ? "equal" : "reject"));
}

export function calculateIntervalMaxValue(interval: Interval, options: AggregateConfigurationOptions): DataValue {
return calculateIntervalMinOrMaxValue(interval, options,
(a: Variant, b: Variant) =>
a.value < b.value ? "select" : (a.value === b.value ? "equal" : "reject") );
a.value < b.value ? "select" : (a.value === b.value ? "equal" : "reject"));
}

// From OPC Unified Architecture, Part 13 26 Release 1.04
Expand Down

0 comments on commit 69ebb4f

Please sign in to comment.