node-opcua-aggregates 2.178.0 → 2.179.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aggregates.d.ts +3 -3
- package/dist/aggregates.js +20 -12
- package/dist/aggregates.js.map +1 -1
- package/dist/average.d.ts +1 -1
- package/dist/average.js +4 -4
- package/dist/average.js.map +1 -1
- package/dist/calculate_bad_good.d.ts +1 -1
- package/dist/calculate_bad_good.js +4 -4
- package/dist/calculate_bad_good.js.map +1 -1
- package/dist/common.d.ts +2 -2
- package/dist/common.js +12 -4
- package/dist/common.js.map +1 -1
- package/dist/count.d.ts +1 -1
- package/dist/count.js +2 -2
- package/dist/count.js.map +1 -1
- package/dist/duration_bad.d.ts +1 -1
- package/dist/duration_bad.js +2 -2
- package/dist/duration_bad.js.map +1 -1
- package/dist/duration_good.d.ts +1 -1
- package/dist/duration_good.js +2 -2
- package/dist/duration_good.js.map +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +18 -18
- package/dist/index.js.map +1 -1
- package/dist/interpolate.d.ts +2 -2
- package/dist/interpolate.js +3 -5
- package/dist/interpolate.js.map +1 -1
- package/dist/interval.d.ts +2 -2
- package/dist/interval.js +22 -16
- package/dist/interval.js.map +1 -1
- package/dist/minmax.d.ts +2 -2
- package/dist/minmax.js +4 -5
- package/dist/minmax.js.map +1 -1
- package/dist/percent_bad.d.ts +1 -1
- package/dist/percent_bad.js +1 -1
- package/dist/percent_bad.js.map +1 -1
- package/dist/percent_good.d.ts +1 -1
- package/dist/percent_good.js +2 -2
- package/dist/percent_good.js.map +1 -1
- package/dist/read_processed_details.d.ts +6 -6
- package/dist/read_processed_details.js +14 -12
- package/dist/read_processed_details.js.map +1 -1
- package/package.json +19 -19
- package/source/aggregates.ts +30 -21
- package/source/average.ts +6 -6
- package/source/calculate_bad_good.ts +8 -9
- package/source/common.ts +18 -10
- package/source/count.ts +4 -4
- package/source/duration_bad.ts +4 -5
- package/source/duration_good.ts +5 -6
- package/source/index.ts +12 -11
- package/source/interpolate.ts +5 -12
- package/source/interval.ts +29 -23
- package/source/minmax.ts +8 -9
- package/source/percent_bad.ts +3 -4
- package/source/percent_good.ts +4 -5
- package/source/read_processed_details.ts +23 -23
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { extraStatusCodeBits, StatusCode, StatusCodes } from "node-opcua-status-code";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import { type AggregateConfigurationOptions, type Interval, isUncertain } from "./interval";
|
|
5
4
|
|
|
6
5
|
const a = (s: StatusCode | undefined, options: AggregateConfigurationOptions) =>
|
|
7
6
|
!s || s === StatusCodes.BadNoData
|
|
8
7
|
? StatusCodes.BadNoData
|
|
9
8
|
: s.isBad() || (options.treatUncertainAsBad && isUncertain(s))
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
? StatusCodes.Bad
|
|
10
|
+
: StatusCodes.Good;
|
|
12
11
|
|
|
13
12
|
function findLowBound(
|
|
14
13
|
interval: Interval,
|
|
@@ -17,7 +16,7 @@ function findLowBound(
|
|
|
17
16
|
const indexStart: number = interval.index;
|
|
18
17
|
|
|
19
18
|
const initialValue = interval.dataValues[indexStart];
|
|
20
|
-
if (initialValue.sourceTimestamp
|
|
19
|
+
if (initialValue.sourceTimestamp?.getTime() === interval.startTime.getTime()) {
|
|
21
20
|
return { previousStatus: initialValue.statusCode, previousTime: interval.startTime.getTime(), indexStart: indexStart + 1 };
|
|
22
21
|
}
|
|
23
22
|
const previousStatus =
|
|
@@ -28,7 +27,6 @@ function findLowBound(
|
|
|
28
27
|
return { previousStatus, previousTime, indexStart };
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
// eslint-disable-next-line max-statements, complexity
|
|
32
30
|
export function calculateBadAndGood(
|
|
33
31
|
interval: Interval,
|
|
34
32
|
options: AggregateConfigurationOptions
|
|
@@ -82,7 +80,10 @@ export function calculateBadAndGood(
|
|
|
82
80
|
if (currentStatus === StatusCodes.BadNoData) {
|
|
83
81
|
partialFlag = extraStatusCodeBits.HistorianPartial;
|
|
84
82
|
}
|
|
85
|
-
|
|
83
|
+
if (!dataValue.sourceTimestamp) {
|
|
84
|
+
throw new Error("calculateBadAndGood: dataValue is missing a sourceTimestamp");
|
|
85
|
+
}
|
|
86
|
+
const currentTime = dataValue.sourceTimestamp.getTime();
|
|
86
87
|
|
|
87
88
|
// debugLog(" ", dataValue.sourceTimestamp?.toISOString(), dataValue.statusCode.toString(), dataValue.value.value);
|
|
88
89
|
|
|
@@ -125,12 +126,10 @@ export function calculateBadAndGood(
|
|
|
125
126
|
const percentGood = (durationGood / effectiveProcessingInterval) * 100;
|
|
126
127
|
const percentBad = (durationBad / effectiveProcessingInterval) * 100;
|
|
127
128
|
|
|
128
|
-
let percentDataGood = options.percentDataGood === undefined ? 100 : options.percentDataGood;
|
|
129
129
|
const percentDataBad = options.percentDataBad === undefined ? 100 : options.percentDataBad;
|
|
130
130
|
|
|
131
131
|
if (percentBad >= percentDataBad || (nbGood === 0 && nbUncertain === 0)) {
|
|
132
132
|
durationGood = 0; // BAD
|
|
133
|
-
percentDataGood = -1;
|
|
134
133
|
// const statusCode = StatusCodes.Bad;
|
|
135
134
|
//return { durationGood, durationBad, durationUnknown, percentBad, percentGood, statusCode };
|
|
136
135
|
}
|
package/source/common.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module node-opca-aggregates
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { type ContinuationPoint, ContinuationPointManager, SessionContext, type UAVariable } from "node-opcua-address-space";
|
|
5
5
|
import { NodeClass } from "node-opcua-data-model";
|
|
6
6
|
import { DataValue } from "node-opcua-data-value";
|
|
7
|
-
import { HistoryData, HistoryReadResult, ReadRawModifiedDetails } from "node-opcua-service-history";
|
|
8
|
-
import { StatusCode, StatusCodes } from "node-opcua-status-code";
|
|
9
7
|
import { coerceNodeId } from "node-opcua-nodeid";
|
|
8
|
+
import { type HistoryData, type HistoryReadResult, ReadRawModifiedDetails } from "node-opcua-service-history";
|
|
9
|
+
import { StatusCode } from "node-opcua-status-code";
|
|
10
10
|
|
|
11
11
|
import { getAggregateConfiguration } from "./aggregates";
|
|
12
|
-
import { getInterval, Interval
|
|
12
|
+
import { type AggregateConfigurationOptionsEx, getInterval, type Interval } from "./interval";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* @internal
|
|
@@ -40,7 +40,7 @@ function processAggregateData(
|
|
|
40
40
|
const dataValue = lambda(interval, aggregateConfiguration);
|
|
41
41
|
|
|
42
42
|
/* c8 ignore next */
|
|
43
|
-
if (!dataValue
|
|
43
|
+
if (!dataValue?.sourceTimestamp) {
|
|
44
44
|
// const dataValue = interval.interpolatedValue(aggregateConfiguration);
|
|
45
45
|
throw Error("invalid DataValue");
|
|
46
46
|
}
|
|
@@ -63,12 +63,14 @@ export function getAggregateData(
|
|
|
63
63
|
): void {
|
|
64
64
|
/* c8 ignore next */
|
|
65
65
|
if (node.nodeClass !== NodeClass.Variable) {
|
|
66
|
-
|
|
66
|
+
callback(new Error("node must be UAVariable"));
|
|
67
|
+
return;
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
/* c8 ignore next */
|
|
70
71
|
if (processingInterval <= 0) {
|
|
71
|
-
|
|
72
|
+
callback(new Error("Invalid processing interval, shall be greater than 0"));
|
|
73
|
+
return;
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
const continuationPointManager = new ContinuationPointManager();
|
|
@@ -99,7 +101,10 @@ export function getAggregateData(
|
|
|
99
101
|
if (err) {
|
|
100
102
|
return callback(err);
|
|
101
103
|
}
|
|
102
|
-
|
|
104
|
+
if (!result) {
|
|
105
|
+
return callback(new Error("getAggregateData: expecting a result when err is null"));
|
|
106
|
+
}
|
|
107
|
+
const historyData = result.historyData as HistoryData;
|
|
103
108
|
|
|
104
109
|
const dataValues = historyData.dataValues || [];
|
|
105
110
|
|
|
@@ -109,9 +114,12 @@ export function getAggregateData(
|
|
|
109
114
|
}
|
|
110
115
|
|
|
111
116
|
export function interpolateValue(dataValue1: DataValue, dataValue2: DataValue, date: Date): DataValue {
|
|
112
|
-
|
|
117
|
+
if (!dataValue1.sourceTimestamp || !dataValue2.sourceTimestamp) {
|
|
118
|
+
throw new Error("interpolateValue: both dataValues must have a sourceTimestamp");
|
|
119
|
+
}
|
|
120
|
+
const t0 = dataValue1.sourceTimestamp.getTime();
|
|
113
121
|
const t = date.getTime();
|
|
114
|
-
const t1 = dataValue2.sourceTimestamp
|
|
122
|
+
const t1 = dataValue2.sourceTimestamp.getTime();
|
|
115
123
|
const coef1 = (t - t0) / (t1 - t0);
|
|
116
124
|
const coef2 = (t1 - t) / (t1 - t0);
|
|
117
125
|
const value = dataValue1.value.clone();
|
package/source/count.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { UAVariable } from "node-opcua-address-space";
|
|
1
|
+
import type { UAVariable } from "node-opcua-address-space";
|
|
2
2
|
import { DataValue } from "node-opcua-data-value";
|
|
3
3
|
import { extraStatusCodeBits, StatusCode, StatusCodes } from "node-opcua-status-code";
|
|
4
4
|
import { DataType } from "node-opcua-variant";
|
|
5
5
|
|
|
6
6
|
import { getAggregateData } from "./common";
|
|
7
|
-
import {
|
|
7
|
+
import { type AggregateConfigurationOptions, type Interval, isUncertain } from "./interval";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* The Count Aggregate retrieves a count of all the raw values within an interval.
|
|
@@ -22,7 +22,7 @@ function calculateCountValue(interval: Interval, options: AggregateConfiguration
|
|
|
22
22
|
let nbUncertain = 0;
|
|
23
23
|
let badDuration = 0;
|
|
24
24
|
let uncertainDuration = 0;
|
|
25
|
-
let
|
|
25
|
+
let _goodDuration = 0;
|
|
26
26
|
for (let i = indexStart; i < indexStart + interval.count; i++) {
|
|
27
27
|
const dataValue = interval.dataValues[i];
|
|
28
28
|
if (dataValue.statusCode.equals(StatusCodes.BadNoData)) {
|
|
@@ -38,7 +38,7 @@ function calculateCountValue(interval: Interval, options: AggregateConfiguration
|
|
|
38
38
|
uncertainDuration += regionDuration;
|
|
39
39
|
} else if (dataValue.statusCode.isGoodish()) {
|
|
40
40
|
nbGood++;
|
|
41
|
-
|
|
41
|
+
_goodDuration += regionDuration;
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
package/source/duration_bad.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { UAVariable } from "node-opcua-address-space";
|
|
1
|
+
import type { UAVariable } from "node-opcua-address-space";
|
|
2
2
|
import { DataValue } from "node-opcua-data-value";
|
|
3
|
-
import { DataType } from "node-opcua-variant";
|
|
4
3
|
import { StatusCodes } from "node-opcua-status-code";
|
|
5
|
-
|
|
6
|
-
import { getAggregateData } from "./common";
|
|
7
|
-
import { Interval, AggregateConfigurationOptions } from "./interval";
|
|
4
|
+
import { DataType } from "node-opcua-variant";
|
|
8
5
|
import { calculateBadAndGood } from "./calculate_bad_good";
|
|
6
|
+
import { getAggregateData } from "./common";
|
|
7
|
+
import type { AggregateConfigurationOptions, Interval } from "./interval";
|
|
9
8
|
|
|
10
9
|
function calculateDurationBad(interval: Interval, options: AggregateConfigurationOptions): DataValue {
|
|
11
10
|
const { durationBad, durationUnknown, statusCode } = calculateBadAndGood(interval, options);
|
package/source/duration_good.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import { UAVariable } from "node-opcua-address-space";
|
|
1
|
+
import type { UAVariable } from "node-opcua-address-space";
|
|
2
2
|
import { DataValue } from "node-opcua-data-value";
|
|
3
|
-
import { DataType } from "node-opcua-variant";
|
|
4
3
|
import { StatusCodes } from "node-opcua-status-code";
|
|
5
|
-
|
|
6
|
-
import { getAggregateData } from "./common";
|
|
7
|
-
import { Interval, AggregateConfigurationOptions } from "./interval";
|
|
4
|
+
import { DataType } from "node-opcua-variant";
|
|
8
5
|
import { calculateBadAndGood } from "./calculate_bad_good";
|
|
6
|
+
import { getAggregateData } from "./common";
|
|
7
|
+
import type { AggregateConfigurationOptions, Interval } from "./interval";
|
|
9
8
|
|
|
10
9
|
function calculateDurationGood(interval: Interval, options: AggregateConfigurationOptions): DataValue {
|
|
11
10
|
const { durationGood, durationUnknown, statusCode } = calculateBadAndGood(interval, options);
|
|
12
|
-
if (durationUnknown>0
|
|
11
|
+
if (durationUnknown > 0 && durationGood === 0) {
|
|
13
12
|
return new DataValue({
|
|
14
13
|
sourceTimestamp: interval.startTime,
|
|
15
14
|
statusCode: StatusCodes.Bad
|
package/source/index.ts
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module node-opca-aggregates
|
|
3
3
|
*/
|
|
4
|
+
|
|
5
|
+
export { AggregateFunction } from "node-opcua-constants";
|
|
4
6
|
export {
|
|
7
|
+
addAggregateFunctionSupport,
|
|
8
|
+
addAggregateStandardFunctionSupport,
|
|
5
9
|
addAggregateSupport,
|
|
6
|
-
installAggregateConfigurationOptions,
|
|
7
10
|
getAggregateConfiguration,
|
|
8
|
-
|
|
9
|
-
addAggregateFunctionSupport
|
|
11
|
+
installAggregateConfigurationOptions
|
|
10
12
|
} from "./aggregates";
|
|
11
|
-
export * from "./interpolate";
|
|
12
|
-
export * from "./minmax";
|
|
13
|
-
export * from "./interval";
|
|
14
|
-
export * from "./common";
|
|
15
13
|
export * from "./average";
|
|
16
|
-
export * from "./
|
|
17
|
-
export { AggregateFunction } from "node-opcua-constants";
|
|
14
|
+
export * from "./common";
|
|
18
15
|
export { getCountData } from "./count";
|
|
19
|
-
export { getPercentGoodData } from "./percent_good";
|
|
20
|
-
export { getPercentBadData } from "./percent_bad";
|
|
21
16
|
export { getDurationBadData } from "./duration_bad";
|
|
22
17
|
export { getDurationGoodData } from "./duration_good";
|
|
18
|
+
export * from "./interpolate";
|
|
19
|
+
export * from "./interval";
|
|
20
|
+
export * from "./minmax";
|
|
21
|
+
export { getPercentBadData } from "./percent_bad";
|
|
22
|
+
export { getPercentGoodData } from "./percent_good";
|
|
23
|
+
export * from "./read_processed_details";
|
package/source/interpolate.ts
CHANGED
|
@@ -37,18 +37,13 @@
|
|
|
37
37
|
// Bound Bad Does not return a Bad bound except as noted above
|
|
38
38
|
// Bound Uncertain Returned Uncertain_DataSubNormal if any Bad value(s) was/were skipped to
|
|
39
39
|
// calculate the bounding value.
|
|
40
|
-
import { UAVariable } from "node-opcua-address-space";
|
|
40
|
+
import type { UAVariable } from "node-opcua-address-space";
|
|
41
41
|
import { assert } from "node-opcua-assert";
|
|
42
42
|
import { DataValue } from "node-opcua-data-value";
|
|
43
43
|
import { StatusCode, StatusCodes } from "node-opcua-status-code";
|
|
44
44
|
|
|
45
45
|
import { getAggregateData, interpolateValue } from "./common";
|
|
46
|
-
import {
|
|
47
|
-
_findGoodDataValueBefore,
|
|
48
|
-
adjustProcessingOptions,
|
|
49
|
-
AggregateConfigurationOptionsEx,
|
|
50
|
-
Interval,
|
|
51
|
-
} from "./interval";
|
|
46
|
+
import { _findGoodDataValueBefore, type AggregateConfigurationOptionsEx, adjustProcessingOptions, type Interval } from "./interval";
|
|
52
47
|
|
|
53
48
|
/*
|
|
54
49
|
For any intervals containing regions where the StatusCodes are Bad,
|
|
@@ -63,10 +58,10 @@ import {
|
|
|
63
58
|
export function interpolatedValue(interval: Interval, options: AggregateConfigurationOptionsEx): DataValue {
|
|
64
59
|
options = adjustProcessingOptions(options);
|
|
65
60
|
|
|
66
|
-
assert(Object.
|
|
67
|
-
assert(Object.
|
|
61
|
+
assert(Object.hasOwn(options, "useSlopedExtrapolation"));
|
|
62
|
+
assert(Object.hasOwn(options, "treatUncertainAsBad"));
|
|
68
63
|
|
|
69
|
-
const bTreatUncertainAsBad = options.treatUncertainAsBad
|
|
64
|
+
const bTreatUncertainAsBad = options.treatUncertainAsBad || false;
|
|
70
65
|
|
|
71
66
|
const steppedValue = (previousDataValue: DataValue): DataValue => {
|
|
72
67
|
if (!previousDataValue.statusCode) {
|
|
@@ -105,7 +100,6 @@ export function interpolatedValue(interval: Interval, options: AggregateConfigur
|
|
|
105
100
|
// else interpolate
|
|
106
101
|
const interpVal = interpolateValue(prev2.dataValue, prev1.dataValue, interval.startTime);
|
|
107
102
|
|
|
108
|
-
// tslint:disable:no-bitwise
|
|
109
103
|
if (prev2.index + 1 < prev1.index || prev1.index < interval.dataValues.length - 1) {
|
|
110
104
|
// some bad data exist in between = change status code
|
|
111
105
|
const mask = 0x0000ffffff;
|
|
@@ -163,7 +157,6 @@ export function interpolatedValue(interval: Interval, options: AggregateConfigur
|
|
|
163
157
|
const interpolatedDataValue = interpolateValue(before.dataValue, next.dataValue, interval.startTime);
|
|
164
158
|
|
|
165
159
|
if (before.index + 1 < next.index || !next.dataValue.statusCode.isGood() || !before.dataValue.statusCode.isGood()) {
|
|
166
|
-
// tslint:disable:no-bitwise
|
|
167
160
|
// some bad data exist in between = change status code
|
|
168
161
|
const mask = 0x0000ffffff;
|
|
169
162
|
const extraBits = interpolatedDataValue.statusCode.value & mask;
|
package/source/interval.ts
CHANGED
|
@@ -2,25 +2,31 @@
|
|
|
2
2
|
* @module node-opca-aggregates
|
|
3
3
|
*/
|
|
4
4
|
import { DataValue } from "node-opcua-data-value";
|
|
5
|
-
import { StatusCode, StatusCodes } from "node-opcua-status-code";
|
|
6
|
-
import { AggregateConfigurationOptions } from "node-opcua-types";
|
|
5
|
+
import { type StatusCode, StatusCodes } from "node-opcua-status-code";
|
|
6
|
+
import type { AggregateConfigurationOptions } from "node-opcua-types";
|
|
7
|
+
|
|
7
8
|
export { AggregateConfigurationOptions } from "node-opcua-types";
|
|
8
9
|
|
|
9
10
|
export interface AggregateConfigurationOptionsEx extends AggregateConfigurationOptions {
|
|
10
11
|
stepped?: boolean;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
|
|
14
14
|
export function isGoodish2(statusCode: StatusCode, { treatUncertainAsBad }: { treatUncertainAsBad?: boolean }): boolean {
|
|
15
15
|
if (statusCode.isGoodish()) return true;
|
|
16
16
|
if (isUncertain(statusCode)) return !treatUncertainAsBad;
|
|
17
17
|
return false;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
21
20
|
export function isUncertain(statusCode: StatusCode): boolean {
|
|
22
21
|
return (statusCode.value & 0x40000000) === 0x40000000 && statusCode.value !== StatusCodes.BadNoData.value;
|
|
23
22
|
}
|
|
23
|
+
function sourceTimestampMs(dataValue: DataValue): number {
|
|
24
|
+
if (!dataValue.sourceTimestamp) {
|
|
25
|
+
throw new Error("expecting dataValue to have a sourceTimestamp");
|
|
26
|
+
}
|
|
27
|
+
return dataValue.sourceTimestamp.getTime();
|
|
28
|
+
}
|
|
29
|
+
|
|
24
30
|
export interface IntervalOptions {
|
|
25
31
|
startTime: Date;
|
|
26
32
|
dataValues: DataValue[];
|
|
@@ -86,9 +92,9 @@ export function adjustProcessingOptions(options: AggregateConfigurationOptionsEx
|
|
|
86
92
|
options = options || {};
|
|
87
93
|
options.treatUncertainAsBad = options.treatUncertainAsBad || false;
|
|
88
94
|
options.useSlopedExtrapolation = options.useSlopedExtrapolation || false;
|
|
89
|
-
options.stepped = options.stepped
|
|
90
|
-
options.percentDataBad = parseInt(options.percentDataBad
|
|
91
|
-
options.percentDataGood = parseInt(options.percentDataGood
|
|
95
|
+
options.stepped = options.stepped || false;
|
|
96
|
+
options.percentDataBad = parseInt(String(options.percentDataBad ?? 0), 10);
|
|
97
|
+
options.percentDataGood = parseInt(String(options.percentDataGood ?? 0), 10);
|
|
92
98
|
return options;
|
|
93
99
|
}
|
|
94
100
|
|
|
@@ -126,7 +132,7 @@ export class Interval {
|
|
|
126
132
|
return false;
|
|
127
133
|
}
|
|
128
134
|
const dataValue1 = this.dataValues[index];
|
|
129
|
-
return this.startTime.getTime() === dataValue1
|
|
135
|
+
return this.startTime.getTime() === sourceTimestampMs(dataValue1);
|
|
130
136
|
}
|
|
131
137
|
|
|
132
138
|
/**
|
|
@@ -144,14 +150,14 @@ export class Interval {
|
|
|
144
150
|
|
|
145
151
|
public toString(): string {
|
|
146
152
|
let str = "";
|
|
147
|
-
str +=
|
|
148
|
-
str +=
|
|
149
|
-
str +=
|
|
150
|
-
str +=
|
|
153
|
+
str += `startTime ${this.startTime.toUTCString()}\n`;
|
|
154
|
+
str += `start ${this.index} `;
|
|
155
|
+
str += `count ${this.count} `;
|
|
156
|
+
str += `isPartial ${this.isPartial}\n`;
|
|
151
157
|
if (this.index >= 0) {
|
|
152
158
|
for (let i = this.index; i < this.index + this.count; i++) {
|
|
153
159
|
const dataValue = this.dataValues[i];
|
|
154
|
-
str +=
|
|
160
|
+
str += ` ${dataValue.sourceTimestamp?.toUTCString()}${dataValue.statusCode.toString()}`;
|
|
155
161
|
str += dataValue.value ? dataValue.value.toString() : "";
|
|
156
162
|
str += "\n";
|
|
157
163
|
}
|
|
@@ -170,16 +176,16 @@ export class Interval {
|
|
|
170
176
|
if (i < 0) {
|
|
171
177
|
return e;
|
|
172
178
|
}
|
|
173
|
-
const lastTimestamp = this.dataValues[i].sourceTimestamp
|
|
174
|
-
return Math.min(e, lastTimestamp.getTime() + 1);
|
|
179
|
+
const lastTimestamp = this.dataValues[i].sourceTimestamp;
|
|
180
|
+
return lastTimestamp ? Math.min(e, lastTimestamp.getTime() + 1) : e;
|
|
175
181
|
}
|
|
176
182
|
|
|
177
183
|
/**
|
|
178
|
-
*
|
|
184
|
+
*
|
|
179
185
|
* @returns the interval duration
|
|
180
186
|
*/
|
|
181
187
|
duration() {
|
|
182
|
-
const t1 = this.dataValues[this.index]
|
|
188
|
+
const t1 = sourceTimestampMs(this.dataValues[this.index]);
|
|
183
189
|
const e = this.getEffectiveEndTime();
|
|
184
190
|
return e - t1;
|
|
185
191
|
}
|
|
@@ -188,9 +194,9 @@ export class Interval {
|
|
|
188
194
|
* returns the region duration starting at index and finishing at index+1 or end limit of the interval
|
|
189
195
|
*/
|
|
190
196
|
regionDuration(index: number): number {
|
|
191
|
-
const t1 = this.dataValues[index]
|
|
197
|
+
const t1 = sourceTimestampMs(this.dataValues[index]);
|
|
192
198
|
const e = this.getEffectiveEndTime();
|
|
193
|
-
const t2 = index < this.dataValues.length - 1 ? Math.min(this.dataValues[index + 1]
|
|
199
|
+
const t2 = index < this.dataValues.length - 1 ? Math.min(sourceTimestampMs(this.dataValues[index + 1]), e) : e;
|
|
194
200
|
return t2 - t1;
|
|
195
201
|
}
|
|
196
202
|
}
|
|
@@ -199,7 +205,7 @@ export function getInterval(startTime: Date, processingInterval: number, indexHi
|
|
|
199
205
|
let count = 0;
|
|
200
206
|
let index = -1;
|
|
201
207
|
for (let i = indexHint; i < dataValues.length; i++) {
|
|
202
|
-
if (dataValues[i]
|
|
208
|
+
if (sourceTimestampMs(dataValues[i]) < startTime.getTime()) {
|
|
203
209
|
continue;
|
|
204
210
|
}
|
|
205
211
|
index = i;
|
|
@@ -208,7 +214,7 @@ export function getInterval(startTime: Date, processingInterval: number, indexHi
|
|
|
208
214
|
|
|
209
215
|
if (index >= 0) {
|
|
210
216
|
for (let i = index; i < dataValues.length; i++) {
|
|
211
|
-
if (dataValues[i]
|
|
217
|
+
if (sourceTimestampMs(dataValues[i]) >= startTime.getTime() + processingInterval) {
|
|
212
218
|
break;
|
|
213
219
|
}
|
|
214
220
|
count++;
|
|
@@ -219,11 +225,11 @@ export function getInterval(startTime: Date, processingInterval: number, indexHi
|
|
|
219
225
|
let isPartial = false;
|
|
220
226
|
if (
|
|
221
227
|
index + count >= dataValues.length &&
|
|
222
|
-
dataValues[dataValues.length - 1]
|
|
228
|
+
sourceTimestampMs(dataValues[dataValues.length - 1]) < startTime.getTime() + processingInterval
|
|
223
229
|
) {
|
|
224
230
|
isPartial = true;
|
|
225
231
|
}
|
|
226
|
-
if (index <= 0 && dataValues[0]
|
|
232
|
+
if (index <= 0 && sourceTimestampMs(dataValues[0]) > startTime.getTime()) {
|
|
227
233
|
isPartial = true;
|
|
228
234
|
}
|
|
229
235
|
|
package/source/minmax.ts
CHANGED
|
@@ -52,18 +52,17 @@
|
|
|
52
52
|
// No End Bound Not Applicable
|
|
53
53
|
// Bound Bad Not Applicable
|
|
54
54
|
// Bound Uncertain Not Applicable
|
|
55
|
-
import { UAVariable } from "node-opcua-address-space";
|
|
55
|
+
import type { UAVariable } from "node-opcua-address-space";
|
|
56
56
|
import { DataValue } from "node-opcua-data-value";
|
|
57
57
|
import { StatusCode, StatusCodes } from "node-opcua-status-code";
|
|
58
|
-
import { Variant } from "node-opcua-variant";
|
|
58
|
+
import type { Variant } from "node-opcua-variant";
|
|
59
59
|
|
|
60
60
|
import { getAggregateData } from "./common";
|
|
61
|
-
import { AggregateConfigurationOptions, Interval } from "./interval";
|
|
61
|
+
import type { AggregateConfigurationOptions, Interval } from "./interval";
|
|
62
62
|
|
|
63
|
-
// eslint-disable-next-line max-statements
|
|
64
63
|
function calculateIntervalMinOrMaxValue(
|
|
65
64
|
interval: Interval,
|
|
66
|
-
|
|
65
|
+
_options: AggregateConfigurationOptions,
|
|
67
66
|
predicate: (a: Variant, b: Variant) => "equal" | "select" | "reject"
|
|
68
67
|
): DataValue {
|
|
69
68
|
// debugLog(interval.toString());
|
|
@@ -94,7 +93,7 @@ function calculateIntervalMinOrMaxValue(
|
|
|
94
93
|
if (!selectedValue) {
|
|
95
94
|
selectedValue = dataValue.value;
|
|
96
95
|
counter = 1;
|
|
97
|
-
if (i === indexStart && dataValue.sourceTimestamp
|
|
96
|
+
if (i === indexStart && dataValue.sourceTimestamp?.getTime() === interval.startTime.getTime()) {
|
|
98
97
|
isRaw = true;
|
|
99
98
|
}
|
|
100
99
|
continue;
|
|
@@ -138,7 +137,7 @@ function calculateIntervalMinOrMaxValue(
|
|
|
138
137
|
return new DataValue({
|
|
139
138
|
sourceTimestamp: interval.startTime,
|
|
140
139
|
statusCode: statusCode as StatusCode,
|
|
141
|
-
value: selectedValue
|
|
140
|
+
value: selectedValue
|
|
142
141
|
});
|
|
143
142
|
}
|
|
144
143
|
|
|
@@ -217,7 +216,7 @@ export function getMinData(
|
|
|
217
216
|
endDate: Date,
|
|
218
217
|
callback: (err: Error | null, dataValues?: DataValue[]) => void
|
|
219
218
|
): void {
|
|
220
|
-
|
|
219
|
+
getAggregateData(node, processingInterval, startDate, endDate, calculateIntervalMinValue, callback);
|
|
221
220
|
}
|
|
222
221
|
|
|
223
222
|
export function getMaxData(
|
|
@@ -227,5 +226,5 @@ export function getMaxData(
|
|
|
227
226
|
endDate: Date,
|
|
228
227
|
callback: (err: Error | null, dataValues?: DataValue[]) => void
|
|
229
228
|
): void {
|
|
230
|
-
|
|
229
|
+
getAggregateData(node, processingInterval, startDate, endDate, calculateIntervalMaxValue, callback);
|
|
231
230
|
}
|
package/source/percent_bad.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { UAVariable } from "node-opcua-address-space";
|
|
1
|
+
import type { UAVariable } from "node-opcua-address-space";
|
|
2
2
|
import { DataValue } from "node-opcua-data-value";
|
|
3
3
|
import { DataType } from "node-opcua-variant";
|
|
4
|
-
|
|
5
|
-
import { getAggregateData } from "./common";
|
|
6
|
-
import { Interval, AggregateConfigurationOptions } from "./interval";
|
|
7
4
|
import { calculateBadAndGood } from "./calculate_bad_good";
|
|
5
|
+
import { getAggregateData } from "./common";
|
|
6
|
+
import type { AggregateConfigurationOptions, Interval } from "./interval";
|
|
8
7
|
|
|
9
8
|
function calculatePercentBad(interval: Interval, options: AggregateConfigurationOptions): DataValue {
|
|
10
9
|
const { percentBad, statusCode } = calculateBadAndGood(interval, options);
|
package/source/percent_good.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { UAVariable } from "node-opcua-address-space";
|
|
1
|
+
import type { UAVariable } from "node-opcua-address-space";
|
|
2
2
|
import { DataValue } from "node-opcua-data-value";
|
|
3
|
-
import { DataType } from "node-opcua-variant";
|
|
4
3
|
import { StatusCodes } from "node-opcua-status-code";
|
|
5
|
-
|
|
6
|
-
import { getAggregateData } from "./common";
|
|
7
|
-
import { Interval, AggregateConfigurationOptions } from "./interval";
|
|
4
|
+
import { DataType } from "node-opcua-variant";
|
|
8
5
|
import { calculateBadAndGood } from "./calculate_bad_good";
|
|
6
|
+
import { getAggregateData } from "./common";
|
|
7
|
+
import type { AggregateConfigurationOptions, Interval } from "./interval";
|
|
9
8
|
|
|
10
9
|
function calculatePercentGood(interval: Interval, options: AggregateConfigurationOptions): DataValue {
|
|
11
10
|
//
|
|
@@ -1,21 +1,19 @@
|
|
|
1
|
+
import type { ContinuationData, ISessionContext, UAVariable } from "node-opcua-address-space";
|
|
1
2
|
import { AggregateFunction } from "node-opcua-constants";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { HistoryData, HistoryReadResult, ReadProcessedDetails } from "node-opcua-service-history";
|
|
9
|
-
|
|
10
|
-
import { getMinData, getMaxData } from "./minmax";
|
|
11
|
-
|
|
12
|
-
import { getInterpolatedData } from "./interpolate";
|
|
3
|
+
import type { QualifiedNameLike } from "node-opcua-data-model";
|
|
4
|
+
import type { DataValue } from "node-opcua-data-value";
|
|
5
|
+
import type { NodeId } from "node-opcua-nodeid";
|
|
6
|
+
import type { NumericRange } from "node-opcua-numeric-range";
|
|
7
|
+
import { HistoryData, HistoryReadResult, type ReadProcessedDetails } from "node-opcua-service-history";
|
|
8
|
+
import { type CallbackT, StatusCodes } from "node-opcua-status-code";
|
|
13
9
|
import { getAverageData } from "./average";
|
|
14
10
|
import { getCountData } from "./count";
|
|
11
|
+
import { getDurationBadData } from "./duration_bad";
|
|
12
|
+
import { getDurationGoodData } from "./duration_good";
|
|
13
|
+
import { getInterpolatedData } from "./interpolate";
|
|
14
|
+
import { getMaxData, getMinData } from "./minmax";
|
|
15
15
|
import { getPercentBadData } from "./percent_bad";
|
|
16
16
|
import { getPercentGoodData } from "./percent_good";
|
|
17
|
-
import { getDurationGoodData } from "./duration_good";
|
|
18
|
-
import { getDurationBadData } from "./duration_bad";
|
|
19
17
|
|
|
20
18
|
function _buildResult(err: Error | null, dataValues: DataValue[] | undefined, callback2: CallbackT<HistoryReadResult>) {
|
|
21
19
|
if (err) {
|
|
@@ -85,11 +83,11 @@ function applyAggregate(
|
|
|
85
83
|
}
|
|
86
84
|
export function readProcessedDetails(
|
|
87
85
|
variable: UAVariable,
|
|
88
|
-
|
|
86
|
+
_context: ISessionContext,
|
|
89
87
|
historyReadDetails: ReadProcessedDetails,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
_indexRange: NumericRange | null,
|
|
89
|
+
_dataEncoding: QualifiedNameLike | null,
|
|
90
|
+
_continuationData: ContinuationData,
|
|
93
91
|
callback: CallbackT<HistoryReadResult>
|
|
94
92
|
): void {
|
|
95
93
|
// OPC Unified Architecture, Part 11 27 Release 1.03
|
|
@@ -143,12 +141,14 @@ export function readProcessedDetails(
|
|
|
143
141
|
const startTime = historyReadDetails.startTime;
|
|
144
142
|
const endTime = historyReadDetails.endTime;
|
|
145
143
|
if (!startTime || !endTime) {
|
|
146
|
-
|
|
144
|
+
callback(null, new HistoryReadResult({ statusCode: StatusCodes.BadInvalidArgument }));
|
|
145
|
+
return;
|
|
147
146
|
}
|
|
148
147
|
if (startTime.getTime() === endTime.getTime()) {
|
|
149
148
|
// Start = End Int = Anything No intervals. Returns a Bad_InvalidArgument StatusCode,
|
|
150
149
|
// regardless of whether there is data at the specified time or not
|
|
151
|
-
|
|
150
|
+
callback(null, new HistoryReadResult({ statusCode: StatusCodes.BadInvalidArgument }));
|
|
151
|
+
return;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
const aggregateTypes: NodeId[] = historyReadDetails.aggregateType || [];
|
|
@@ -157,9 +157,9 @@ export function readProcessedDetails(
|
|
|
157
157
|
// starting at startTime and ending at endTime.
|
|
158
158
|
const processingInterval = historyReadDetails.processingInterval || endTime.getTime() - startTime.getTime();
|
|
159
159
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
return
|
|
160
|
+
if (historyReadDetails.aggregateType?.length !== 1) {
|
|
161
|
+
callback(null, new HistoryReadResult({ statusCode: StatusCodes.BadInternalError }));
|
|
162
|
+
return;
|
|
163
163
|
}
|
|
164
|
-
|
|
164
|
+
applyAggregate(variable, processingInterval, startTime, endTime, aggregateTypes[0], callback);
|
|
165
165
|
}
|