node-opcua-aggregates 2.77.0 → 2.79.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 +19 -4
- package/dist/aggregates.js +95 -20
- package/dist/aggregates.js.map +1 -1
- package/dist/calculate_bad_good.d.ts +10 -0
- package/dist/calculate_bad_good.js +125 -0
- package/dist/calculate_bad_good.js.map +1 -0
- package/dist/common.js +1 -7
- package/dist/common.js.map +1 -1
- package/dist/count.d.ts +3 -0
- package/dist/count.js +87 -0
- package/dist/count.js.map +1 -0
- package/dist/duration_bad.d.ts +4 -0
- package/dist/duration_bad.js +33 -0
- package/dist/duration_bad.js.map +1 -0
- package/dist/duration_good.d.ts +4 -0
- package/dist/duration_good.js +33 -0
- package/dist/duration_good.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/interval.d.ts +17 -1
- package/dist/interval.js +52 -5
- package/dist/interval.js.map +1 -1
- package/dist/percent_bad.d.ts +4 -0
- package/dist/percent_bad.js +26 -0
- package/dist/percent_bad.js.map +1 -0
- package/dist/percent_good.d.ts +7 -0
- package/dist/percent_good.js +50 -0
- package/dist/percent_good.js.map +1 -0
- package/dist/read_processed_details.js +44 -19
- package/dist/read_processed_details.js.map +1 -1
- package/package.json +11 -11
- package/source/aggregates.ts +118 -62
- package/source/calculate_bad_good.ts +148 -0
- package/source/common.ts +3 -9
- package/source/count.ts +98 -0
- package/source/duration_bad.ts +37 -0
- package/source/duration_good.ts +37 -0
- package/source/index.ts +7 -1
- package/source/interval.ts +53 -4
- package/source/percent_bad.ts +30 -0
- package/source/percent_good.ts +55 -0
- package/source/read_processed_details.ts +44 -23
package/source/interval.ts
CHANGED
|
@@ -13,10 +13,20 @@ export function isGoodish(statusCode: StatusCode): boolean {
|
|
|
13
13
|
return statusCode.value < 0x40000000;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
export function isGoodish2(statusCode: StatusCode, { treatUncertainAsBad }: { treatUncertainAsBad?: boolean }): boolean {
|
|
17
|
+
if (isGoodish(statusCode)) return true;
|
|
18
|
+
if (isUncertain(statusCode)) return !treatUncertainAsBad;
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
export function isBad(statusCode: StatusCode): boolean {
|
|
17
23
|
return statusCode.value >= 0x80000000;
|
|
18
24
|
}
|
|
19
25
|
|
|
26
|
+
export function isUncertain(statusCode: StatusCode): boolean {
|
|
27
|
+
return (statusCode.value & 0x40000000) === 0x40000000 && statusCode.value !== StatusCodes.BadNoData.value;
|
|
28
|
+
}
|
|
29
|
+
|
|
20
30
|
export function isGood(statusCode: StatusCode): boolean {
|
|
21
31
|
return statusCode.value === 0x0;
|
|
22
32
|
}
|
|
@@ -27,6 +37,7 @@ export interface IntervalOptions {
|
|
|
27
37
|
index: number;
|
|
28
38
|
count: number;
|
|
29
39
|
isPartial: boolean;
|
|
40
|
+
processingInterval: number;
|
|
30
41
|
}
|
|
31
42
|
|
|
32
43
|
interface DataValueWithIndex {
|
|
@@ -97,6 +108,7 @@ export class Interval {
|
|
|
97
108
|
public index: number;
|
|
98
109
|
public count: number;
|
|
99
110
|
public isPartial: boolean;
|
|
111
|
+
public processingInterval: number;
|
|
100
112
|
|
|
101
113
|
// startTime
|
|
102
114
|
// dataValues
|
|
@@ -108,6 +120,7 @@ export class Interval {
|
|
|
108
120
|
this.index = options.index;
|
|
109
121
|
this.count = options.count;
|
|
110
122
|
this.isPartial = options.isPartial;
|
|
123
|
+
this.processingInterval = options.processingInterval;
|
|
111
124
|
}
|
|
112
125
|
|
|
113
126
|
public getPercentBad(): number {
|
|
@@ -155,9 +168,44 @@ export class Interval {
|
|
|
155
168
|
}
|
|
156
169
|
return str;
|
|
157
170
|
}
|
|
171
|
+
public getEffectiveEndTime(): number {
|
|
172
|
+
const e = this.startTime.getTime() + this.processingInterval;
|
|
173
|
+
if (!this.dataValues || this.dataValues.length === 0) {
|
|
174
|
+
return e;
|
|
175
|
+
}
|
|
176
|
+
let i = this.dataValues.length - 1;
|
|
177
|
+
while (i >= 0 && this.dataValues[i].statusCode === StatusCodes.BadNoData) {
|
|
178
|
+
i--;
|
|
179
|
+
}
|
|
180
|
+
if (i < 0) {
|
|
181
|
+
return e;
|
|
182
|
+
}
|
|
183
|
+
const lastTimestamp = this.dataValues[i].sourceTimestamp!;
|
|
184
|
+
return Math.min(e, lastTimestamp.getTime() + 1);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
*
|
|
189
|
+
* @returns the interval duration
|
|
190
|
+
*/
|
|
191
|
+
duration() {
|
|
192
|
+
const t1 = this.dataValues[this.index].sourceTimestamp!.getTime();
|
|
193
|
+
const e = this.getEffectiveEndTime();
|
|
194
|
+
return e - t1;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* returns the region duration starting at index and finishing at index+1 or end limit of the interval
|
|
199
|
+
*/
|
|
200
|
+
regionDuration(index: number): number {
|
|
201
|
+
const t1 = this.dataValues[index].sourceTimestamp!.getTime();
|
|
202
|
+
const e = this.getEffectiveEndTime();
|
|
203
|
+
const t2 = index < this.dataValues.length - 1 ? Math.min(this.dataValues[index + 1].sourceTimestamp!.getTime(), e) : e;
|
|
204
|
+
return t2 - t1;
|
|
205
|
+
}
|
|
158
206
|
}
|
|
159
207
|
|
|
160
|
-
export function getInterval(startTime: Date,
|
|
208
|
+
export function getInterval(startTime: Date, processingInterval: number, indexHint: number, dataValues: DataValue[]): Interval {
|
|
161
209
|
let count = 0;
|
|
162
210
|
let index = -1;
|
|
163
211
|
for (let i = indexHint; i < dataValues.length; i++) {
|
|
@@ -170,7 +218,7 @@ export function getInterval(startTime: Date, duration: number, indexHint: number
|
|
|
170
218
|
|
|
171
219
|
if (index >= 0) {
|
|
172
220
|
for (let i = index; i < dataValues.length; i++) {
|
|
173
|
-
if (dataValues[i].sourceTimestamp!.getTime() >= startTime.getTime() +
|
|
221
|
+
if (dataValues[i].sourceTimestamp!.getTime() >= startTime.getTime() + processingInterval) {
|
|
174
222
|
break;
|
|
175
223
|
}
|
|
176
224
|
count++;
|
|
@@ -181,7 +229,7 @@ export function getInterval(startTime: Date, duration: number, indexHint: number
|
|
|
181
229
|
let isPartial = false;
|
|
182
230
|
if (
|
|
183
231
|
index + count >= dataValues.length &&
|
|
184
|
-
dataValues[dataValues.length - 1].sourceTimestamp!.getTime() < startTime.getTime() +
|
|
232
|
+
dataValues[dataValues.length - 1].sourceTimestamp!.getTime() < startTime.getTime() + processingInterval
|
|
185
233
|
) {
|
|
186
234
|
isPartial = true;
|
|
187
235
|
}
|
|
@@ -194,6 +242,7 @@ export function getInterval(startTime: Date, duration: number, indexHint: number
|
|
|
194
242
|
dataValues,
|
|
195
243
|
index,
|
|
196
244
|
isPartial,
|
|
197
|
-
startTime
|
|
245
|
+
startTime,
|
|
246
|
+
processingInterval
|
|
198
247
|
});
|
|
199
248
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { UAVariable } from "node-opcua-address-space";
|
|
2
|
+
import { DataValue } from "node-opcua-data-value";
|
|
3
|
+
import { DataType } from "node-opcua-variant";
|
|
4
|
+
|
|
5
|
+
import { getAggregateData } from "./common";
|
|
6
|
+
import { Interval, AggregateConfigurationOptions, isGoodish } from "./interval";
|
|
7
|
+
import { calculateBadAndGood } from "./calculate_bad_good";
|
|
8
|
+
|
|
9
|
+
function calculatePercentBad(interval: Interval, options: AggregateConfigurationOptions): DataValue {
|
|
10
|
+
const { percentBad, statusCode } = calculateBadAndGood(interval, options);
|
|
11
|
+
const value = percentBad;
|
|
12
|
+
if (isGoodish(statusCode)) {
|
|
13
|
+
return new DataValue({
|
|
14
|
+
sourceTimestamp: interval.startTime,
|
|
15
|
+
statusCode,
|
|
16
|
+
value: { dataType: DataType.Double, value }
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return new DataValue({ sourceTimestamp: interval.startTime, statusCode, value: { dataType: DataType.Null } });
|
|
20
|
+
}
|
|
21
|
+
/**Retrieve the percentage of data (0 to 100) in the interval which has Bad StatusCode. */
|
|
22
|
+
export function getPercentBadData(
|
|
23
|
+
node: UAVariable,
|
|
24
|
+
processingInterval: number,
|
|
25
|
+
startDate: Date,
|
|
26
|
+
endDate: Date,
|
|
27
|
+
callback: (err: Error | null, dataValues?: DataValue[]) => void
|
|
28
|
+
): void {
|
|
29
|
+
getAggregateData(node, processingInterval, startDate, endDate, calculatePercentBad, callback);
|
|
30
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { UAVariable } from "node-opcua-address-space";
|
|
2
|
+
import { DataValue } from "node-opcua-data-value";
|
|
3
|
+
import { Variant, DataType } from "node-opcua-variant";
|
|
4
|
+
import { extraStatusCodeBits, StatusCode, StatusCodes } from "node-opcua-status-code";
|
|
5
|
+
|
|
6
|
+
import { getAggregateData } from "./common";
|
|
7
|
+
import { Interval, AggregateConfigurationOptions, isGood, isGoodish, isUncertain, isBad } from "./interval";
|
|
8
|
+
import { calculateBadAndGood } from "./calculate_bad_good";
|
|
9
|
+
|
|
10
|
+
function calculatePercentGood(interval: Interval, options: AggregateConfigurationOptions): DataValue {
|
|
11
|
+
//
|
|
12
|
+
// The PercentGood Aggregate defined in Table 44 performs the following calculation:
|
|
13
|
+
//
|
|
14
|
+
// PercentGood = DurationGood / ProcessingInterval x 100
|
|
15
|
+
// where:
|
|
16
|
+
//
|
|
17
|
+
// DurationGood is the result from the DurationGood *Aggregate*, calculated using the *ProcessingInterval* supplied to *PercentGood* call.
|
|
18
|
+
// ProcessingInterval is the duration of interval.
|
|
19
|
+
// If the last interval is a partial interval then the duration of the partial interval is used in the
|
|
20
|
+
// calculation.
|
|
21
|
+
// Each Aggregate is returned with timestamp of the start of the interval. StatusCodes are Good, Calculated.
|
|
22
|
+
//
|
|
23
|
+
const { percentGood, statusCode } = calculateBadAndGood(interval, options);
|
|
24
|
+
if (percentGood < 0) {
|
|
25
|
+
// special case ! to indicate that no good pointhas been found in the interval
|
|
26
|
+
return new DataValue({
|
|
27
|
+
sourceTimestamp: interval.startTime,
|
|
28
|
+
statusCode: StatusCodes.Bad,
|
|
29
|
+
value: { dataType: DataType.Null }
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const value = percentGood;
|
|
33
|
+
if (isGoodish(statusCode)) {
|
|
34
|
+
return new DataValue({
|
|
35
|
+
sourceTimestamp: interval.startTime,
|
|
36
|
+
statusCode,
|
|
37
|
+
value: { dataType: DataType.Double, value }
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return new DataValue({ sourceTimestamp: interval.startTime, statusCode, value: { dataType: DataType.Null } });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
*
|
|
45
|
+
* @param node Retrieve the percentage of data (0 to 100) in the interval which has Good StatusCode.
|
|
46
|
+
*/
|
|
47
|
+
export function getPercentGoodData(
|
|
48
|
+
node: UAVariable,
|
|
49
|
+
processingInterval: number,
|
|
50
|
+
startDate: Date,
|
|
51
|
+
endDate: Date,
|
|
52
|
+
callback: (err: Error | null, dataValues?: DataValue[]) => void
|
|
53
|
+
): void {
|
|
54
|
+
getAggregateData(node, processingInterval, startDate, endDate, calculatePercentGood, callback);
|
|
55
|
+
}
|
|
@@ -5,16 +5,17 @@ import { QualifiedNameLike } from "node-opcua-data-model";
|
|
|
5
5
|
import { CallbackT, StatusCodes } from "node-opcua-status-code";
|
|
6
6
|
import { DataValue } from "node-opcua-data-value";
|
|
7
7
|
import { NodeId } from "node-opcua-nodeid";
|
|
8
|
-
import {
|
|
9
|
-
HistoryData,
|
|
10
|
-
HistoryReadResult,
|
|
11
|
-
ReadProcessedDetails
|
|
12
|
-
} from "node-opcua-service-history";
|
|
8
|
+
import { HistoryData, HistoryReadResult, ReadProcessedDetails } from "node-opcua-service-history";
|
|
13
9
|
|
|
14
10
|
import { getMinData, getMaxData } from "./minmax";
|
|
15
11
|
|
|
16
12
|
import { getInterpolatedData } from "./interpolate";
|
|
17
13
|
import { getAverageData } from "./average";
|
|
14
|
+
import { getCountData } from "./count";
|
|
15
|
+
import { getPercentBadData } from "./percent_bad";
|
|
16
|
+
import { getPercentGoodData } from "./percent_good";
|
|
17
|
+
import { getDurationGoodData } from "./duration_good";
|
|
18
|
+
import { getDurationBadData } from "./duration_bad";
|
|
18
19
|
|
|
19
20
|
function _buildResult(err: Error | null, dataValues: DataValue[] | undefined, callback2: CallbackT<HistoryReadResult>) {
|
|
20
21
|
if (err) {
|
|
@@ -43,23 +44,43 @@ function applyAggregate(
|
|
|
43
44
|
const buildResult = (err: Error | null, dataValues: DataValue[] | undefined) => {
|
|
44
45
|
_buildResult(err, dataValues, callback2);
|
|
45
46
|
};
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
47
|
+
if (aggregateType.namespace === 0) {
|
|
48
|
+
switch (aggregateType.value) {
|
|
49
|
+
case AggregateFunction.Minimum:
|
|
50
|
+
getMinData(variable, processingInterval, startTime, endTime, buildResult);
|
|
51
|
+
break;
|
|
52
|
+
case AggregateFunction.Maximum:
|
|
53
|
+
getMaxData(variable, processingInterval, startTime, endTime, buildResult);
|
|
54
|
+
break;
|
|
55
|
+
case AggregateFunction.Interpolative:
|
|
56
|
+
getInterpolatedData(variable, processingInterval, startTime, endTime, buildResult);
|
|
57
|
+
break;
|
|
58
|
+
case AggregateFunction.Average:
|
|
59
|
+
getAverageData(variable, processingInterval, startTime, endTime, buildResult);
|
|
60
|
+
break;
|
|
61
|
+
case AggregateFunction.DurationGood:
|
|
62
|
+
getDurationGoodData(variable, processingInterval, startTime, endTime, buildResult);
|
|
63
|
+
break;
|
|
64
|
+
case AggregateFunction.DurationBad:
|
|
65
|
+
getDurationBadData(variable, processingInterval, startTime, endTime, buildResult);
|
|
66
|
+
break;
|
|
67
|
+
case AggregateFunction.PercentBad:
|
|
68
|
+
getPercentBadData(variable, processingInterval, startTime, endTime, buildResult);
|
|
69
|
+
break;
|
|
70
|
+
case AggregateFunction.PercentGood:
|
|
71
|
+
getPercentGoodData(variable, processingInterval, startTime, endTime, buildResult);
|
|
72
|
+
break;
|
|
73
|
+
case AggregateFunction.Count:
|
|
74
|
+
getCountData(variable, processingInterval, startTime, endTime, buildResult);
|
|
75
|
+
break;
|
|
76
|
+
default:
|
|
77
|
+
// todo provide correct implementation
|
|
78
|
+
return callback2(null, new HistoryReadResult({ statusCode: StatusCodes.BadAggregateNotSupported }));
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
// custom aggregate added by some companion specification
|
|
82
|
+
// to do
|
|
83
|
+
return callback2(null, new HistoryReadResult({ statusCode: StatusCodes.BadAggregateNotSupported }));
|
|
63
84
|
}
|
|
64
85
|
}
|
|
65
86
|
export function readProcessedDetails(
|
|
@@ -137,7 +158,7 @@ export function readProcessedDetails(
|
|
|
137
158
|
const processingInterval = historyReadDetails.processingInterval || endTime.getTime() - startTime.getTime();
|
|
138
159
|
|
|
139
160
|
// tslint:disable-next-line: prefer-for-of
|
|
140
|
-
if (historyReadDetails.aggregateType
|
|
161
|
+
if (!historyReadDetails.aggregateType || historyReadDetails.aggregateType.length !== 1) {
|
|
141
162
|
return callback(null, new HistoryReadResult({ statusCode: StatusCodes.BadInternalError }));
|
|
142
163
|
}
|
|
143
164
|
return applyAggregate(variable, processingInterval, startTime, endTime, aggregateTypes[0], callback);
|