node-opcua-address-space 2.186.9 → 2.186.12
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/impl/historical_access/address_space_historical_data_node.d.ts +3 -1
- package/dist/impl/historical_access/address_space_historical_data_node.d.ts.map +1 -1
- package/dist/impl/historical_access/address_space_historical_data_node.js +142 -1
- package/dist/impl/historical_access/address_space_historical_data_node.js.map +1 -1
- package/dist/tsconfig_base.tsbuildinfo +1 -1
- package/impl/historical_access/address_space_historical_data_node.ts +188 -2
- package/package.json +18 -18
|
@@ -14,7 +14,7 @@ import type {
|
|
|
14
14
|
} from "node-opcua-address-space-base";
|
|
15
15
|
import { assert } from "node-opcua-assert";
|
|
16
16
|
import { AccessLevelFlag, NodeClass, type QualifiedNameLike } from "node-opcua-data-model";
|
|
17
|
-
import
|
|
17
|
+
import { DataValue } from "node-opcua-data-value";
|
|
18
18
|
import { isMinDate, minDate } from "node-opcua-date-time";
|
|
19
19
|
import { make_warningLog } from "node-opcua-debug";
|
|
20
20
|
import type { UAHistoricalDataConfiguration } from "node-opcua-nodeset-ua";
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
ReadProcessedDetails,
|
|
28
28
|
ReadRawModifiedDetails
|
|
29
29
|
} from "node-opcua-service-history";
|
|
30
|
-
import { type CallbackT, StatusCodes } from "node-opcua-status-code";
|
|
30
|
+
import { type CallbackT, StatusCode, StatusCodes } from "node-opcua-status-code";
|
|
31
31
|
import { DataType } from "node-opcua-variant";
|
|
32
32
|
import type { AddressSpacePrivate } from "../address_space_private.js";
|
|
33
33
|
import { historizerFactoryHolder } from "../historizer_factory.js";
|
|
@@ -88,6 +88,32 @@ function inInTimeRange2(historyReadDetails: ReadRawModifiedDetails, dataValue: D
|
|
|
88
88
|
);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
function _findNearestBefore(q: Dequeue<DataValue>, timeMs: number): DataValue | null {
|
|
92
|
+
let result: DataValue | null = null;
|
|
93
|
+
let c = q.head.next;
|
|
94
|
+
while (c.data) {
|
|
95
|
+
const ts = (c.data.sourceTimestamp || minDate).getTime();
|
|
96
|
+
if (ts >= timeMs) {
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
result = c.data;
|
|
100
|
+
c = c.next;
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function _findNearestAfter(q: Dequeue<DataValue>, timeMs: number): DataValue | null {
|
|
106
|
+
let c = q.head.next;
|
|
107
|
+
while (c.data) {
|
|
108
|
+
const ts = (c.data.sourceTimestamp || minDate).getTime();
|
|
109
|
+
if (ts > timeMs) {
|
|
110
|
+
return c.data;
|
|
111
|
+
}
|
|
112
|
+
c = c.next;
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
91
117
|
function filter_dequeue(
|
|
92
118
|
q: Dequeue<DataValue>,
|
|
93
119
|
historyReadRawModifiedDetails: ReadRawModifiedDetails,
|
|
@@ -201,6 +227,14 @@ export class VariableHistorian implements IVariableHistorian {
|
|
|
201
227
|
}
|
|
202
228
|
callback(null, dataValues);
|
|
203
229
|
}
|
|
230
|
+
|
|
231
|
+
public findBoundBefore(date: Date): DataValue | null {
|
|
232
|
+
return _findNearestBefore(this._timeline, date.getTime());
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
public findBoundAfter(date: Date): DataValue | null {
|
|
236
|
+
return _findNearestAfter(this._timeline, date.getTime());
|
|
237
|
+
}
|
|
204
238
|
}
|
|
205
239
|
|
|
206
240
|
function _get_startOfOfflineArchive(node: UAVariableImpl) {
|
|
@@ -354,6 +388,139 @@ function _historyReadRawAsync(
|
|
|
354
388
|
this.varHistorian.extractDataValues(historyReadRawModifiedDetails, maxNumberToExtract, isReversed, reverseDataValue, callback);
|
|
355
389
|
}
|
|
356
390
|
|
|
391
|
+
/**
|
|
392
|
+
* Compute a single Bounding Value for the raw-read time domain (OPC 10000-11 6.5.3.2).
|
|
393
|
+
*
|
|
394
|
+
* `before` and `after` are the nearest raw values immediately before and after
|
|
395
|
+
* `boundaryTime` (either may come from outside the requested [startTime, endTime] window).
|
|
396
|
+
* Per 3.1.2 BoundingValues, when neither is missing the Server "determines" a value
|
|
397
|
+
* associated with the boundary time itself, built from that neighbouring data; this
|
|
398
|
+
* function follows the same rule the HistoricalDataConfiguration already uses for the
|
|
399
|
+
* Interpolative Aggregate (Part 13): a sloped linear interpolation, or a held value when
|
|
400
|
+
* the node is configured as Stepped or the value is not numeric.
|
|
401
|
+
*
|
|
402
|
+
* When one side is missing there is nothing to determine the value from, so the result
|
|
403
|
+
* is Bad_BoundNotFound with a null value at the boundary time, as required by 6.5.3.2.
|
|
404
|
+
*/
|
|
405
|
+
function _computeBoundingValue(
|
|
406
|
+
node: UAVariableImpl,
|
|
407
|
+
before: DataValue | null,
|
|
408
|
+
after: DataValue | null,
|
|
409
|
+
boundaryTime: Date
|
|
410
|
+
): DataValue {
|
|
411
|
+
if (!before || !after || !before.sourceTimestamp || !after.sourceTimestamp) {
|
|
412
|
+
return new DataValue({
|
|
413
|
+
sourceTimestamp: boundaryTime,
|
|
414
|
+
statusCode: StatusCodes.BadBoundNotFound,
|
|
415
|
+
value: { dataType: DataType.Null }
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const stepped = node.$historicalDataConfiguration?.stepped?.readValue()?.value?.value === true;
|
|
420
|
+
const beforeValue = before.value?.value;
|
|
421
|
+
const afterValue = after.value?.value;
|
|
422
|
+
if (stepped || typeof beforeValue !== "number" || typeof afterValue !== "number") {
|
|
423
|
+
// stepped display, or a value that cannot be placed on a slope: hold the value that
|
|
424
|
+
// was in effect at the boundary time (the sample immediately preceding it).
|
|
425
|
+
return new DataValue({
|
|
426
|
+
sourceTimestamp: boundaryTime,
|
|
427
|
+
statusCode: StatusCode.makeStatusCode(before.statusCode, "HistorianInterpolated"),
|
|
428
|
+
value: before.value
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const t0 = before.sourceTimestamp.getTime();
|
|
433
|
+
const t1 = after.sourceTimestamp.getTime();
|
|
434
|
+
const t = boundaryTime.getTime();
|
|
435
|
+
const coef1 = (t - t0) / (t1 - t0);
|
|
436
|
+
const coef2 = (t1 - t) / (t1 - t0);
|
|
437
|
+
const value = before.value.clone();
|
|
438
|
+
value.value = coef2 * beforeValue + coef1 * afterValue;
|
|
439
|
+
return new DataValue({
|
|
440
|
+
sourceTimestamp: boundaryTime,
|
|
441
|
+
statusCode: StatusCode.makeStatusCode(before.statusCode, "HistorianInterpolated"),
|
|
442
|
+
value
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Add the start and end Bounding Values to a raw-read result when returnBounds is set
|
|
448
|
+
* (OPC 10000-11 6.5.3.2 and 4.6). Only called when both startTime and endTime were
|
|
449
|
+
* specified: the single-sided requests (only startTime, or only endTime) are read to the
|
|
450
|
+
* edge of the online archive rather than a bounded window, so there is no second boundary
|
|
451
|
+
* to compute a bound against, and are left unbounded here.
|
|
452
|
+
*
|
|
453
|
+
* `dataValues` is in the order the client will receive it (already reversed when
|
|
454
|
+
* `reverseDataValue` is set); this function restores chronological order to reason about
|
|
455
|
+
* "first" and "last", then re-applies the same order on the way out.
|
|
456
|
+
*/
|
|
457
|
+
function _addBoundingValues(
|
|
458
|
+
node: UAVariableImpl,
|
|
459
|
+
details: ReadRawModifiedDetails,
|
|
460
|
+
dataValues: DataValue[],
|
|
461
|
+
reverseDataValue: boolean
|
|
462
|
+
): DataValue[] {
|
|
463
|
+
const historian = node.varHistorian;
|
|
464
|
+
const startTime = details.startTime as Date;
|
|
465
|
+
const endTime = details.endTime as Date;
|
|
466
|
+
|
|
467
|
+
const findBefore = (date: Date): DataValue | null => (historian?.findBoundBefore ? historian.findBoundBefore(date) : null);
|
|
468
|
+
const findAfter = (date: Date): DataValue | null => (historian?.findBoundAfter ? historian.findBoundAfter(date) : null);
|
|
469
|
+
const boundNotSupported = (boundaryTime: Date): DataValue =>
|
|
470
|
+
new DataValue({
|
|
471
|
+
sourceTimestamp: boundaryTime,
|
|
472
|
+
statusCode: StatusCodes.BadBoundNotSupported,
|
|
473
|
+
value: { dataType: DataType.Null }
|
|
474
|
+
});
|
|
475
|
+
const supportsBounds = !!(historian?.findBoundBefore && historian.findBoundAfter);
|
|
476
|
+
|
|
477
|
+
const ascending = reverseDataValue ? dataValues.slice().reverse() : dataValues.slice();
|
|
478
|
+
|
|
479
|
+
if (startTime.getTime() === endTime.getTime()) {
|
|
480
|
+
// a request for a single instant: the value at that instant, if any, is already
|
|
481
|
+
// part of dataValues, so there is at most one bound to add.
|
|
482
|
+
if (ascending.length === 0) {
|
|
483
|
+
const bound = supportsBounds
|
|
484
|
+
? _computeBoundingValue(node, findBefore(startTime), findAfter(startTime), startTime)
|
|
485
|
+
: boundNotSupported(startTime);
|
|
486
|
+
ascending.push(bound);
|
|
487
|
+
}
|
|
488
|
+
return reverseDataValue ? ascending.reverse() : ascending;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const result: DataValue[] = [];
|
|
492
|
+
|
|
493
|
+
const hasExactStart = ascending.length > 0 && ascending[0].sourceTimestamp?.getTime() === startTime.getTime();
|
|
494
|
+
if (!hasExactStart) {
|
|
495
|
+
const bound = supportsBounds
|
|
496
|
+
? _computeBoundingValue(
|
|
497
|
+
node,
|
|
498
|
+
findBefore(startTime),
|
|
499
|
+
ascending.length > 0 ? ascending[0] : findAfter(startTime),
|
|
500
|
+
startTime
|
|
501
|
+
)
|
|
502
|
+
: boundNotSupported(startTime);
|
|
503
|
+
result.push(bound);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
result.push(...ascending);
|
|
507
|
+
|
|
508
|
+
const hasExactEnd = ascending.length > 0 && ascending[ascending.length - 1].sourceTimestamp?.getTime() === endTime.getTime();
|
|
509
|
+
if (!hasExactEnd) {
|
|
510
|
+
const bound = supportsBounds
|
|
511
|
+
? _computeBoundingValue(
|
|
512
|
+
node,
|
|
513
|
+
ascending.length > 0 ? ascending[ascending.length - 1] : findBefore(endTime),
|
|
514
|
+
findAfter(endTime),
|
|
515
|
+
endTime
|
|
516
|
+
)
|
|
517
|
+
: boundNotSupported(endTime);
|
|
518
|
+
result.push(bound);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return reverseDataValue ? result.reverse() : result;
|
|
522
|
+
}
|
|
523
|
+
|
|
357
524
|
function _historyReadRaw(
|
|
358
525
|
this: UAVariable,
|
|
359
526
|
context: ISessionContext,
|
|
@@ -534,6 +701,22 @@ function _historyReadRaw(
|
|
|
534
701
|
return callback(err);
|
|
535
702
|
}
|
|
536
703
|
|
|
704
|
+
// Bounding Values (6.4.3.2 / 6.5.3.2): only meaningful when the request has both
|
|
705
|
+
// ends of a time domain to bound. See _addBoundingValues for why the single-sided
|
|
706
|
+
// requests (only startTime, or only endTime) are left unbounded.
|
|
707
|
+
if (
|
|
708
|
+
historyReadRawModifiedDetails.returnBounds &&
|
|
709
|
+
!isMinDate(historyReadRawModifiedDetails.startTime) &&
|
|
710
|
+
!isMinDate(historyReadRawModifiedDetails.endTime)
|
|
711
|
+
) {
|
|
712
|
+
dataValues = _addBoundingValues(
|
|
713
|
+
this as UAVariableImpl,
|
|
714
|
+
historyReadRawModifiedDetails,
|
|
715
|
+
dataValues,
|
|
716
|
+
reverseDataValue
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
|
|
537
720
|
const cnt = session.continuationPointManager.registerHistoryReadRaw(
|
|
538
721
|
historyReadRawModifiedDetails.numValuesPerNode,
|
|
539
722
|
dataValues,
|
|
@@ -590,6 +773,7 @@ function _historyRead(
|
|
|
590
773
|
statusCode: StatusCodes.BadUserAccessDenied
|
|
591
774
|
});
|
|
592
775
|
callback(null, result);
|
|
776
|
+
return;
|
|
593
777
|
}
|
|
594
778
|
|
|
595
779
|
assert(typeof callback === "function");
|
|
@@ -846,4 +1030,6 @@ export function AddressSpace_installHistoricalDataNode(
|
|
|
846
1030
|
// update the index of historizing nodes in the addressSpace
|
|
847
1031
|
node.addressSpace.historizingNodes = node.addressSpace.historizingNodes || new Set();
|
|
848
1032
|
node.addressSpace.historizingNodes.add(node);
|
|
1033
|
+
|
|
1034
|
+
node.addressSpace.onHistorizingNodeAdded?.(node);
|
|
849
1035
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-opcua-address-space",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.186.
|
|
4
|
+
"version": "2.186.12",
|
|
5
5
|
"description": "pure nodejs OPCUA SDK - module address-space",
|
|
6
6
|
"main": "./dist/api/index.js",
|
|
7
7
|
"types": "./dist/api/index.d.ts",
|
|
@@ -23,35 +23,35 @@
|
|
|
23
23
|
"@types/semver": "7.8.0",
|
|
24
24
|
"chalk": "6.0.1",
|
|
25
25
|
"dequeue": "^1.0.5",
|
|
26
|
-
"node-opcua-address-space-base": "2.186.
|
|
26
|
+
"node-opcua-address-space-base": "2.186.10",
|
|
27
27
|
"node-opcua-assert": "2.186.7",
|
|
28
28
|
"node-opcua-basic-types": "2.186.7",
|
|
29
29
|
"node-opcua-binary-stream": "2.186.7",
|
|
30
|
-
"node-opcua-client-dynamic-extension-object": "2.186.
|
|
30
|
+
"node-opcua-client-dynamic-extension-object": "2.186.12",
|
|
31
31
|
"node-opcua-constants": "2.185.0",
|
|
32
32
|
"node-opcua-crypto": "6.1.2",
|
|
33
|
-
"node-opcua-data-access": "2.186.
|
|
34
|
-
"node-opcua-data-model": "2.186.
|
|
35
|
-
"node-opcua-data-value": "2.186.
|
|
33
|
+
"node-opcua-data-access": "2.186.10",
|
|
34
|
+
"node-opcua-data-model": "2.186.10",
|
|
35
|
+
"node-opcua-data-value": "2.186.10",
|
|
36
36
|
"node-opcua-date-time": "2.186.7",
|
|
37
37
|
"node-opcua-debug": "2.186.7",
|
|
38
38
|
"node-opcua-enum": "2.186.7",
|
|
39
39
|
"node-opcua-extension-object": "2.186.7",
|
|
40
40
|
"node-opcua-factory": "2.186.7",
|
|
41
41
|
"node-opcua-nodeid": "2.186.7",
|
|
42
|
-
"node-opcua-nodeset-ua": "2.186.
|
|
42
|
+
"node-opcua-nodeset-ua": "2.186.10",
|
|
43
43
|
"node-opcua-numeric-range": "2.186.7",
|
|
44
44
|
"node-opcua-object-registry": "2.186.7",
|
|
45
|
-
"node-opcua-pseudo-session": "2.186.
|
|
46
|
-
"node-opcua-service-browse": "2.186.
|
|
47
|
-
"node-opcua-service-call": "2.186.
|
|
48
|
-
"node-opcua-service-history": "2.186.
|
|
49
|
-
"node-opcua-service-translate-browse-path": "2.186.
|
|
50
|
-
"node-opcua-service-write": "2.186.
|
|
45
|
+
"node-opcua-pseudo-session": "2.186.12",
|
|
46
|
+
"node-opcua-service-browse": "2.186.10",
|
|
47
|
+
"node-opcua-service-call": "2.186.10",
|
|
48
|
+
"node-opcua-service-history": "2.186.10",
|
|
49
|
+
"node-opcua-service-translate-browse-path": "2.186.10",
|
|
50
|
+
"node-opcua-service-write": "2.186.10",
|
|
51
51
|
"node-opcua-status-code": "2.186.7",
|
|
52
|
-
"node-opcua-types": "2.186.
|
|
52
|
+
"node-opcua-types": "2.186.10",
|
|
53
53
|
"node-opcua-utils": "2.186.7",
|
|
54
|
-
"node-opcua-variant": "2.186.
|
|
54
|
+
"node-opcua-variant": "2.186.10",
|
|
55
55
|
"node-opcua-xml2json": "2.186.7",
|
|
56
56
|
"semver": "^7.8.5",
|
|
57
57
|
"thenify-ex": "4.4.0",
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
"node-opcua-leak-detector": "2.186.7",
|
|
64
64
|
"node-opcua-nodesets": "2.185.0",
|
|
65
65
|
"node-opcua-packet-analyzer": "2.186.7",
|
|
66
|
-
"node-opcua-schemas": "2.186.
|
|
67
|
-
"node-opcua-service-filter": "2.186.
|
|
66
|
+
"node-opcua-schemas": "2.186.10",
|
|
67
|
+
"node-opcua-service-filter": "2.186.10",
|
|
68
68
|
"node-opcua-test-fixtures": "2.184.0",
|
|
69
69
|
"node-opcua-test-helpers": "2.184.0"
|
|
70
70
|
},
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"internet of things"
|
|
84
84
|
],
|
|
85
85
|
"homepage": "http://node-opcua.github.io/",
|
|
86
|
-
"gitHead": "
|
|
86
|
+
"gitHead": "0c9e997639836bd5eacb1a297c00a66702dbb18b",
|
|
87
87
|
"files": [
|
|
88
88
|
"dist",
|
|
89
89
|
"distHelpers",
|