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.
Files changed (43) hide show
  1. package/dist/aggregates.d.ts +19 -4
  2. package/dist/aggregates.js +95 -20
  3. package/dist/aggregates.js.map +1 -1
  4. package/dist/calculate_bad_good.d.ts +10 -0
  5. package/dist/calculate_bad_good.js +125 -0
  6. package/dist/calculate_bad_good.js.map +1 -0
  7. package/dist/common.js +1 -7
  8. package/dist/common.js.map +1 -1
  9. package/dist/count.d.ts +3 -0
  10. package/dist/count.js +87 -0
  11. package/dist/count.js.map +1 -0
  12. package/dist/duration_bad.d.ts +4 -0
  13. package/dist/duration_bad.js +33 -0
  14. package/dist/duration_bad.js.map +1 -0
  15. package/dist/duration_good.d.ts +4 -0
  16. package/dist/duration_good.js +33 -0
  17. package/dist/duration_good.js.map +1 -0
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.js +3 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/interval.d.ts +17 -1
  22. package/dist/interval.js +52 -5
  23. package/dist/interval.js.map +1 -1
  24. package/dist/percent_bad.d.ts +4 -0
  25. package/dist/percent_bad.js +26 -0
  26. package/dist/percent_bad.js.map +1 -0
  27. package/dist/percent_good.d.ts +7 -0
  28. package/dist/percent_good.js +50 -0
  29. package/dist/percent_good.js.map +1 -0
  30. package/dist/read_processed_details.js +44 -19
  31. package/dist/read_processed_details.js.map +1 -1
  32. package/package.json +11 -11
  33. package/source/aggregates.ts +118 -62
  34. package/source/calculate_bad_good.ts +148 -0
  35. package/source/common.ts +3 -9
  36. package/source/count.ts +98 -0
  37. package/source/duration_bad.ts +37 -0
  38. package/source/duration_good.ts +37 -0
  39. package/source/index.ts +7 -1
  40. package/source/interval.ts +53 -4
  41. package/source/percent_bad.ts +30 -0
  42. package/source/percent_good.ts +55 -0
  43. package/source/read_processed_details.ts +44 -23
@@ -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, duration: number, indexHint: number, dataValues: DataValue[]): Interval {
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() + duration) {
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() + duration
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
- switch (aggregateType.value) {
47
- case AggregateFunction.Minimum:
48
- getMinData(variable, processingInterval, startTime, endTime, buildResult);
49
- break;
50
- case AggregateFunction.Maximum:
51
- getMaxData(variable, processingInterval, startTime, endTime, buildResult);
52
- break;
53
- case AggregateFunction.Interpolative:
54
- getInterpolatedData(variable, processingInterval, startTime, endTime, buildResult);
55
- break;
56
- case AggregateFunction.Average:
57
- getAverageData(variable, processingInterval, startTime, endTime, buildResult);
58
- break;
59
- case AggregateFunction.Count:
60
- default:
61
- // todo provide correct implementation
62
- return callback2(null, new HistoryReadResult({ statusCode: StatusCodes.BadAggregateNotSupported }));
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?.length !== 1) {
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);