node-opcua-server 2.80.0 → 2.82.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/base_server.js +10 -1
- package/dist/base_server.js.map +1 -1
- package/dist/monitored_item.d.ts +2 -3
- package/dist/monitored_item.js +69 -32
- package/dist/monitored_item.js.map +1 -1
- package/dist/node_sampler.js +4 -0
- package/dist/node_sampler.js.map +1 -1
- package/dist/opcua_server.js.map +1 -1
- package/dist/server_engine.js +38 -21
- package/dist/server_engine.js.map +1 -1
- package/dist/server_subscription.js +5 -4
- package/dist/server_subscription.js.map +1 -1
- package/dist/validate_filter.js +26 -6
- package/dist/validate_filter.js.map +1 -1
- package/package.json +44 -44
- package/source/base_server.ts +15 -3
- package/source/monitored_item.ts +81 -44
- package/source/node_sampler.ts +6 -0
- package/source/opcua_server.ts +0 -1
- package/source/server_engine.ts +62 -27
- package/source/server_subscription.ts +11 -8
- package/source/validate_filter.ts +28 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-opcua-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.82.0",
|
|
4
4
|
"description": "pure nodejs OPCUA SDK - module -server",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "tsc -b",
|
|
@@ -17,57 +17,57 @@
|
|
|
17
17
|
"chalk": "4.1.2",
|
|
18
18
|
"dequeue": "^1.0.5",
|
|
19
19
|
"lodash": "4.17.21",
|
|
20
|
-
"node-opcua-address-space": "2.
|
|
21
|
-
"node-opcua-address-space-base": "2.
|
|
20
|
+
"node-opcua-address-space": "2.82.0",
|
|
21
|
+
"node-opcua-address-space-base": "2.82.0",
|
|
22
22
|
"node-opcua-assert": "2.77.0",
|
|
23
|
-
"node-opcua-basic-types": "2.
|
|
24
|
-
"node-opcua-binary-stream": "2.
|
|
25
|
-
"node-opcua-certificate-manager": "2.
|
|
26
|
-
"node-opcua-client": "2.
|
|
27
|
-
"node-opcua-client-dynamic-extension-object": "2.
|
|
28
|
-
"node-opcua-common": "2.
|
|
23
|
+
"node-opcua-basic-types": "2.82.0",
|
|
24
|
+
"node-opcua-binary-stream": "2.82.0",
|
|
25
|
+
"node-opcua-certificate-manager": "2.82.0",
|
|
26
|
+
"node-opcua-client": "2.82.0",
|
|
27
|
+
"node-opcua-client-dynamic-extension-object": "2.82.0",
|
|
28
|
+
"node-opcua-common": "2.82.0",
|
|
29
29
|
"node-opcua-constants": "2.77.0",
|
|
30
|
-
"node-opcua-crypto": "^1.
|
|
31
|
-
"node-opcua-data-model": "2.
|
|
32
|
-
"node-opcua-data-value": "2.
|
|
33
|
-
"node-opcua-date-time": "2.
|
|
34
|
-
"node-opcua-debug": "2.
|
|
35
|
-
"node-opcua-extension-object": "2.
|
|
36
|
-
"node-opcua-factory": "2.
|
|
30
|
+
"node-opcua-crypto": "^1.12.0",
|
|
31
|
+
"node-opcua-data-model": "2.82.0",
|
|
32
|
+
"node-opcua-data-value": "2.82.0",
|
|
33
|
+
"node-opcua-date-time": "2.82.0",
|
|
34
|
+
"node-opcua-debug": "2.82.0",
|
|
35
|
+
"node-opcua-extension-object": "2.82.0",
|
|
36
|
+
"node-opcua-factory": "2.82.0",
|
|
37
37
|
"node-opcua-hostname": "2.77.0",
|
|
38
|
-
"node-opcua-nodeid": "2.
|
|
38
|
+
"node-opcua-nodeid": "2.82.0",
|
|
39
39
|
"node-opcua-nodesets": "2.77.0",
|
|
40
|
-
"node-opcua-numeric-range": "2.
|
|
41
|
-
"node-opcua-object-registry": "2.
|
|
42
|
-
"node-opcua-secure-channel": "2.
|
|
43
|
-
"node-opcua-service-browse": "2.
|
|
44
|
-
"node-opcua-service-call": "2.
|
|
45
|
-
"node-opcua-service-discovery": "2.
|
|
46
|
-
"node-opcua-service-endpoints": "2.
|
|
47
|
-
"node-opcua-service-filter": "2.
|
|
48
|
-
"node-opcua-service-history": "2.
|
|
49
|
-
"node-opcua-service-node-management": "2.
|
|
50
|
-
"node-opcua-service-query": "2.
|
|
51
|
-
"node-opcua-service-read": "2.
|
|
52
|
-
"node-opcua-service-register-node": "2.
|
|
53
|
-
"node-opcua-service-secure-channel": "2.
|
|
54
|
-
"node-opcua-service-session": "2.
|
|
55
|
-
"node-opcua-service-subscription": "2.
|
|
56
|
-
"node-opcua-service-translate-browse-path": "2.
|
|
57
|
-
"node-opcua-service-write": "2.
|
|
58
|
-
"node-opcua-status-code": "2.
|
|
59
|
-
"node-opcua-types": "2.
|
|
60
|
-
"node-opcua-utils": "2.
|
|
61
|
-
"node-opcua-variant": "2.
|
|
40
|
+
"node-opcua-numeric-range": "2.82.0",
|
|
41
|
+
"node-opcua-object-registry": "2.82.0",
|
|
42
|
+
"node-opcua-secure-channel": "2.82.0",
|
|
43
|
+
"node-opcua-service-browse": "2.82.0",
|
|
44
|
+
"node-opcua-service-call": "2.82.0",
|
|
45
|
+
"node-opcua-service-discovery": "2.82.0",
|
|
46
|
+
"node-opcua-service-endpoints": "2.82.0",
|
|
47
|
+
"node-opcua-service-filter": "2.82.0",
|
|
48
|
+
"node-opcua-service-history": "2.82.0",
|
|
49
|
+
"node-opcua-service-node-management": "2.82.0",
|
|
50
|
+
"node-opcua-service-query": "2.82.0",
|
|
51
|
+
"node-opcua-service-read": "2.82.0",
|
|
52
|
+
"node-opcua-service-register-node": "2.82.0",
|
|
53
|
+
"node-opcua-service-secure-channel": "2.82.0",
|
|
54
|
+
"node-opcua-service-session": "2.82.0",
|
|
55
|
+
"node-opcua-service-subscription": "2.82.0",
|
|
56
|
+
"node-opcua-service-translate-browse-path": "2.82.0",
|
|
57
|
+
"node-opcua-service-write": "2.82.0",
|
|
58
|
+
"node-opcua-status-code": "2.82.0",
|
|
59
|
+
"node-opcua-types": "2.82.0",
|
|
60
|
+
"node-opcua-utils": "2.82.0",
|
|
61
|
+
"node-opcua-variant": "2.82.0",
|
|
62
62
|
"thenify": "^3.3.1"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
-
"node-opcua-data-access": "2.
|
|
66
|
-
"node-opcua-leak-detector": "2.
|
|
67
|
-
"node-opcua-pki": "^2.
|
|
65
|
+
"node-opcua-data-access": "2.82.0",
|
|
66
|
+
"node-opcua-leak-detector": "2.82.0",
|
|
67
|
+
"node-opcua-pki": "^2.18.0",
|
|
68
68
|
"node-opcua-test-helpers": "2.77.0",
|
|
69
69
|
"should": "^13.2.3",
|
|
70
|
-
"sinon": "^14.0.
|
|
70
|
+
"sinon": "^14.0.1",
|
|
71
71
|
"underscore": "^1.13.6"
|
|
72
72
|
},
|
|
73
73
|
"author": "Etienne Rossignon",
|
|
@@ -85,5 +85,5 @@
|
|
|
85
85
|
"internet of things"
|
|
86
86
|
],
|
|
87
87
|
"homepage": "http://node-opcua.github.io/",
|
|
88
|
-
"gitHead": "
|
|
88
|
+
"gitHead": "2f66e1fe69c825305f94b15ca17cff066aa37496"
|
|
89
89
|
}
|
package/source/base_server.ts
CHANGED
|
@@ -20,7 +20,7 @@ import { coerceLocalizedText, LocalizedText } from "node-opcua-data-model";
|
|
|
20
20
|
import { installPeriodicClockAdjustment, uninstallPeriodicClockAdjustment } from "node-opcua-date-time";
|
|
21
21
|
import { checkDebugFlag, make_debugLog, make_errorLog } from "node-opcua-debug";
|
|
22
22
|
import { displayTraceFromThisProjectOnly } from "node-opcua-debug";
|
|
23
|
-
import { extractFullyQualifiedDomainName, getHostname, resolveFullyQualifiedDomainName } from "node-opcua-hostname";
|
|
23
|
+
import { extractFullyQualifiedDomainName, getFullyQualifiedDomainName, getHostname, resolveFullyQualifiedDomainName } from "node-opcua-hostname";
|
|
24
24
|
import { Message, Response, ServerSecureChannelLayer, ServerSecureChannelParent } from "node-opcua-secure-channel";
|
|
25
25
|
import { FindServersRequest, FindServersResponse } from "node-opcua-service-discovery";
|
|
26
26
|
import { ApplicationType, GetEndpointsResponse } from "node-opcua-service-endpoints";
|
|
@@ -179,14 +179,26 @@ export class OPCUABaseServer extends OPCUASecureObject {
|
|
|
179
179
|
if (fs.existsSync(this.certificateFile)) {
|
|
180
180
|
return;
|
|
181
181
|
}
|
|
182
|
+
|
|
183
|
+
// collect all hostnames
|
|
184
|
+
const hostnames = [];
|
|
185
|
+
for (const e of this.endpoints) {
|
|
186
|
+
for( const ee of e.endpointDescriptions()) {
|
|
187
|
+
/* to do */
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
182
191
|
const lockfile = path.join(this.certificateFile + ".lock");
|
|
183
192
|
await withLock({ lockfile }, async () => {
|
|
184
193
|
if (!fs.existsSync(this.certificateFile)) {
|
|
185
194
|
const applicationUri = this.serverInfo.applicationUri!;
|
|
195
|
+
const fqdn = getFullyQualifiedDomainName();
|
|
186
196
|
const hostname = getHostname();
|
|
197
|
+
const dns = [... new Set([fqdn, hostname])];
|
|
198
|
+
|
|
187
199
|
await this.serverCertificateManager.createSelfSignedCertificate({
|
|
188
200
|
applicationUri,
|
|
189
|
-
dns
|
|
201
|
+
dns,
|
|
190
202
|
// ip: await getIpAddresses(),
|
|
191
203
|
outputFile: this.certificateFile,
|
|
192
204
|
|
|
@@ -198,7 +210,7 @@ export class OPCUABaseServer extends OPCUASecureObject {
|
|
|
198
210
|
}
|
|
199
211
|
});
|
|
200
212
|
}
|
|
201
|
-
|
|
213
|
+
|
|
202
214
|
public async initializeCM(): Promise<void> {
|
|
203
215
|
await this.serverCertificateManager.initialize();
|
|
204
216
|
await this.createDefaultCertificate();
|
package/source/monitored_item.ts
CHANGED
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
isOutsideDeadbandPercent,
|
|
41
41
|
PseudoRange
|
|
42
42
|
} from "node-opcua-service-subscription";
|
|
43
|
-
import { StatusCode, StatusCodes } from "node-opcua-status-code";
|
|
43
|
+
import { CallbackT, StatusCode, StatusCodes } from "node-opcua-status-code";
|
|
44
44
|
import {
|
|
45
45
|
DataChangeNotification,
|
|
46
46
|
EventFieldList,
|
|
@@ -56,7 +56,6 @@ import { appendToTimer, removeFromTimer } from "./node_sampler";
|
|
|
56
56
|
import { validateFilter } from "./validate_filter";
|
|
57
57
|
import { checkWhereClauseOnAdressSpace } from "./filter/check_where_clause_on_address_space";
|
|
58
58
|
import { SamplingFunc } from "./sampling_func";
|
|
59
|
-
import { registerNodePromoter } from "node-opcua-address-space/source/loader/register_node_promoter";
|
|
60
59
|
|
|
61
60
|
export type QueueItem = MonitoredItemNotification | EventFieldList;
|
|
62
61
|
|
|
@@ -473,7 +472,7 @@ export class MonitoredItem extends EventEmitter {
|
|
|
473
472
|
// sampling interval.
|
|
474
473
|
const recordInitialValue =
|
|
475
474
|
old_monitoringMode === MonitoringMode.Invalid || old_monitoringMode === MonitoringMode.Disabled;
|
|
476
|
-
|
|
475
|
+
const installEventHandler = old_monitoringMode === MonitoringMode.Invalid;
|
|
477
476
|
this._start_sampling(recordInitialValue);
|
|
478
477
|
}
|
|
479
478
|
}
|
|
@@ -555,11 +554,16 @@ export class MonitoredItem extends EventEmitter {
|
|
|
555
554
|
*
|
|
556
555
|
*/
|
|
557
556
|
public recordValue(dataValue: DataValue, skipChangeTest: boolean, indexRange?: NumericRange): void {
|
|
558
|
-
|
|
557
|
+
|
|
558
|
+
if (!this.itemToMonitor) {
|
|
559
|
+
// we must have a valid itemToMonitor(have this monitoredItem been disposed already ?)
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
559
563
|
assert(dataValue !== this.oldDataValue, "recordValue expects different dataValue to be provided");
|
|
560
564
|
|
|
561
565
|
assert(
|
|
562
|
-
!dataValue.value || dataValue.value !== this.oldDataValue!.value,
|
|
566
|
+
!dataValue.value || !this.oldDataValue || dataValue.value !== this.oldDataValue!.value,
|
|
563
567
|
"recordValue expects different dataValue.value to be provided"
|
|
564
568
|
);
|
|
565
569
|
|
|
@@ -581,7 +585,6 @@ export class MonitoredItem extends EventEmitter {
|
|
|
581
585
|
}
|
|
582
586
|
}
|
|
583
587
|
|
|
584
|
-
assert(this.itemToMonitor, "must have a valid itemToMonitor(have this monitoredItem been disposed already ?");
|
|
585
588
|
// extract the range that we are interested with
|
|
586
589
|
dataValue = extractRange(dataValue, this.itemToMonitor.indexRange);
|
|
587
590
|
|
|
@@ -760,7 +763,7 @@ export class MonitoredItem extends EventEmitter {
|
|
|
760
763
|
|
|
761
764
|
this._adjust_queue_to_match_new_queue_size();
|
|
762
765
|
|
|
763
|
-
this.
|
|
766
|
+
this._adjustSampling(old_samplingInterval);
|
|
764
767
|
|
|
765
768
|
if (monitoringParameters.filter) {
|
|
766
769
|
const statusCodeFilter = validateFilter(monitoringParameters.filter, this.itemToMonitor, this.node!);
|
|
@@ -784,12 +787,11 @@ export class MonitoredItem extends EventEmitter {
|
|
|
784
787
|
}
|
|
785
788
|
|
|
786
789
|
public async resendInitialValues(): Promise<void> {
|
|
787
|
-
//
|
|
790
|
+
// the first Publish response(s) after the TransferSubscriptions call shall contain the current values of all
|
|
788
791
|
// Monitored Items in the Subscription where the Monitoring Mode is set to Reporting.
|
|
789
792
|
// the first Publish response after the TransferSubscriptions call shall contain only the value changes since
|
|
790
793
|
// the last Publish response was sent.
|
|
791
794
|
// This parameter only applies to MonitoredItems used for monitoring Attribute changes.
|
|
792
|
-
this._stop_sampling();
|
|
793
795
|
return this._start_sampling(true);
|
|
794
796
|
}
|
|
795
797
|
|
|
@@ -804,10 +806,13 @@ export class MonitoredItem extends EventEmitter {
|
|
|
804
806
|
/**
|
|
805
807
|
* @method _on_sampling_timer
|
|
806
808
|
* @private
|
|
807
|
-
* request
|
|
808
|
-
*
|
|
809
809
|
*/
|
|
810
810
|
private _on_sampling_timer() {
|
|
811
|
+
|
|
812
|
+
if (this.monitoringMode === MonitoringMode.Disabled) {
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
|
|
811
816
|
const sessionContext = this.getSessionContext();
|
|
812
817
|
if (!sessionContext) {
|
|
813
818
|
return;
|
|
@@ -971,13 +976,34 @@ export class MonitoredItem extends EventEmitter {
|
|
|
971
976
|
return this.$subscription.$session;
|
|
972
977
|
}
|
|
973
978
|
|
|
974
|
-
private _start_sampling(recordInitialValue
|
|
979
|
+
private _start_sampling(recordInitialValue: boolean): void {
|
|
975
980
|
// istanbul ignore next
|
|
976
981
|
if (!this.node) {
|
|
977
982
|
throw new Error("Internal Error");
|
|
978
983
|
}
|
|
979
984
|
setImmediate(() => this.__start_sampling(recordInitialValue));
|
|
980
985
|
}
|
|
986
|
+
|
|
987
|
+
private __acquireInitialValue(sessionContext: ISessionContext, callback: CallbackT<DataValue>): void {
|
|
988
|
+
// aquire initial value from the variable/object not itself or from the last known value if we have
|
|
989
|
+
// one already
|
|
990
|
+
assert(this.itemToMonitor.attributeId === AttributeIds.Value);
|
|
991
|
+
assert(this.node);
|
|
992
|
+
if (this.node?.nodeClass !== NodeClass.Variable) {
|
|
993
|
+
return callback(new Error("Invalid "));
|
|
994
|
+
}
|
|
995
|
+
const variable = (this.node as UAVariable);
|
|
996
|
+
if (!this.oldDataValue || this.oldDataValue.statusCode == StatusCodes.BadDataUnavailable) {
|
|
997
|
+
variable.readValueAsync(sessionContext, (err: Error | null, dataValue?: DataValue) => {
|
|
998
|
+
callback(err, dataValue);
|
|
999
|
+
});
|
|
1000
|
+
} else {
|
|
1001
|
+
const o = this.oldDataValue;
|
|
1002
|
+
this.oldDataValue = new DataValue({ statusCode: StatusCodes.BadDataUnavailable });
|
|
1003
|
+
callback(null, o);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
}
|
|
981
1007
|
private __start_sampling(recordInitialValue?: boolean): void {
|
|
982
1008
|
// istanbul ignore next
|
|
983
1009
|
if (!this.node) {
|
|
@@ -989,8 +1015,6 @@ export class MonitoredItem extends EventEmitter {
|
|
|
989
1015
|
if (!sessionContext) {
|
|
990
1016
|
return;
|
|
991
1017
|
}
|
|
992
|
-
// make sure oldDataValue is scrapped so first data recording can happen
|
|
993
|
-
this.oldDataValue = new DataValue({ statusCode: StatusCodes.BadDataUnavailable }); // unset initially
|
|
994
1018
|
|
|
995
1019
|
this._stop_sampling();
|
|
996
1020
|
|
|
@@ -999,10 +1023,11 @@ export class MonitoredItem extends EventEmitter {
|
|
|
999
1023
|
if (doDebug) {
|
|
1000
1024
|
debugLog("xxxxxx monitoring EventNotifier on", this.node.nodeId.toString(), this.node.browseName.toString());
|
|
1001
1025
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1026
|
+
if (!this._on_opcua_event_received_callback) {
|
|
1027
|
+
// we are monitoring OPCUA Event
|
|
1028
|
+
this._on_opcua_event_received_callback = this._on_opcua_event.bind(this);
|
|
1029
|
+
this.node.on("event", this._on_opcua_event_received_callback);
|
|
1030
|
+
}
|
|
1006
1031
|
return;
|
|
1007
1032
|
}
|
|
1008
1033
|
if (this.itemToMonitor.attributeId !== AttributeIds.Value) {
|
|
@@ -1010,10 +1035,11 @@ export class MonitoredItem extends EventEmitter {
|
|
|
1010
1035
|
this.samplingInterval = 0; // turned to exception-based regardless of requested sampling interval
|
|
1011
1036
|
|
|
1012
1037
|
// non value attribute only react on value change
|
|
1013
|
-
this._attribute_changed_callback
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1038
|
+
if (!this._attribute_changed_callback) {
|
|
1039
|
+
this._attribute_changed_callback = this._on_value_changed.bind(this);
|
|
1040
|
+
const event_name = makeAttributeEventName(this.itemToMonitor.attributeId);
|
|
1041
|
+
this.node.on(event_name, this._attribute_changed_callback);
|
|
1042
|
+
}
|
|
1017
1043
|
|
|
1018
1044
|
if (recordInitialValue) {
|
|
1019
1045
|
// read initial value
|
|
@@ -1026,29 +1052,32 @@ export class MonitoredItem extends EventEmitter {
|
|
|
1026
1052
|
if (this.samplingInterval === 0) {
|
|
1027
1053
|
// we have a exception-based dataItem : event based model, so we do not need a timer
|
|
1028
1054
|
// rather , we setup the "value_changed_event";
|
|
1029
|
-
this._value_changed_callback
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1055
|
+
if (!this._value_changed_callback) {
|
|
1056
|
+
assert(!this._semantic_changed_callback);
|
|
1057
|
+
this._value_changed_callback = this._on_value_changed.bind(this);
|
|
1058
|
+
this._semantic_changed_callback = this._on_semantic_changed.bind(this);
|
|
1059
|
+
this.node.on("value_changed", this._value_changed_callback);
|
|
1060
|
+
this.node.on("semantic_changed", this._semantic_changed_callback);
|
|
1061
|
+
}
|
|
1034
1062
|
|
|
1035
1063
|
// initiate first read
|
|
1036
1064
|
if (recordInitialValue) {
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
}
|
|
1042
|
-
resolve();
|
|
1043
|
-
});
|
|
1065
|
+
this.__acquireInitialValue(sessionContext, (err: Error | null, dataValue?: DataValue) => {
|
|
1066
|
+
if (!err && dataValue) {
|
|
1067
|
+
this.recordValue(dataValue, true);
|
|
1068
|
+
}
|
|
1044
1069
|
});
|
|
1045
1070
|
}
|
|
1046
1071
|
} else {
|
|
1047
|
-
this._set_timer();
|
|
1048
1072
|
if (recordInitialValue) {
|
|
1049
|
-
|
|
1050
|
-
|
|
1073
|
+
this.__acquireInitialValue(sessionContext, (err: Error | null, dataValue?: DataValue) => {
|
|
1074
|
+
if (!err && dataValue) {
|
|
1075
|
+
this.recordValue(dataValue, true);
|
|
1076
|
+
}
|
|
1077
|
+
this._set_timer();
|
|
1051
1078
|
});
|
|
1079
|
+
} else {
|
|
1080
|
+
this._set_timer();
|
|
1052
1081
|
}
|
|
1053
1082
|
}
|
|
1054
1083
|
}
|
|
@@ -1065,9 +1094,10 @@ export class MonitoredItem extends EventEmitter {
|
|
|
1065
1094
|
// that the data item is exception-based rather than being sampled at some period. An exception-based
|
|
1066
1095
|
// model means that the underlying system does not require sampling and reports data changes.
|
|
1067
1096
|
if (this.node && this.node.nodeClass === NodeClass.Variable) {
|
|
1097
|
+
const variable = (this.node as UAVariable);
|
|
1068
1098
|
this.samplingInterval = _adjust_sampling_interval(
|
|
1069
1099
|
monitoredParameters.samplingInterval,
|
|
1070
|
-
|
|
1100
|
+
variable.minimumSamplingInterval || 0
|
|
1071
1101
|
);
|
|
1072
1102
|
} else {
|
|
1073
1103
|
this.samplingInterval = _adjust_sampling_interval(monitoredParameters.samplingInterval, 0);
|
|
@@ -1197,11 +1227,11 @@ export class MonitoredItem extends EventEmitter {
|
|
|
1197
1227
|
) {
|
|
1198
1228
|
throw new Error(
|
|
1199
1229
|
"dataValue.value.value cannot be the same object twice! " +
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1230
|
+
this.node!.browseName.toString() +
|
|
1231
|
+
" " +
|
|
1232
|
+
dataValue.toString() +
|
|
1233
|
+
" " +
|
|
1234
|
+
chalk.cyan(this.oldDataValue.toString())
|
|
1205
1235
|
);
|
|
1206
1236
|
}
|
|
1207
1237
|
|
|
@@ -1248,6 +1278,13 @@ export class MonitoredItem extends EventEmitter {
|
|
|
1248
1278
|
}
|
|
1249
1279
|
|
|
1250
1280
|
private _set_timer() {
|
|
1281
|
+
|
|
1282
|
+
if(!this.itemToMonitor) {
|
|
1283
|
+
// item has already been deleted
|
|
1284
|
+
// so do not create the timer !
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1251
1288
|
assert(this.samplingInterval >= MonitoredItem.minimumSamplingInterval);
|
|
1252
1289
|
assert(!this._samplingId);
|
|
1253
1290
|
|
|
@@ -1289,7 +1326,7 @@ export class MonitoredItem extends EventEmitter {
|
|
|
1289
1326
|
assert(this.queue.length <= this.queueSize);
|
|
1290
1327
|
}
|
|
1291
1328
|
|
|
1292
|
-
private
|
|
1329
|
+
private _adjustSampling(old_samplingInterval: number) {
|
|
1293
1330
|
if (old_samplingInterval !== this.samplingInterval) {
|
|
1294
1331
|
this._start_sampling(false);
|
|
1295
1332
|
}
|
package/source/node_sampler.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { assert } from "node-opcua-assert";
|
|
5
5
|
import { checkDebugFlag, make_debugLog } from "node-opcua-debug";
|
|
6
|
+
import { MonitoringMode } from "node-opcua-types";
|
|
6
7
|
import { hrtime } from "node-opcua-utils";
|
|
7
8
|
|
|
8
9
|
const debugLog = make_debugLog(__filename);
|
|
@@ -18,6 +19,11 @@ interface MonitoredItemPriv {
|
|
|
18
19
|
}
|
|
19
20
|
function sampleMonitoredItem(monitoredItem: MonitoredItem) {
|
|
20
21
|
const _monitoredItem = monitoredItem as unknown as MonitoredItemPriv;
|
|
22
|
+
|
|
23
|
+
if (monitoredItem.monitoringMode === MonitoringMode.Disabled) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
21
27
|
setImmediate(() => {
|
|
22
28
|
_monitoredItem._on_sampling_timer();
|
|
23
29
|
});
|
package/source/opcua_server.ts
CHANGED
|
@@ -1154,7 +1154,6 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1154
1154
|
securityModes: options.securityModes,
|
|
1155
1155
|
securityPolicies: options.securityPolicies
|
|
1156
1156
|
});
|
|
1157
|
-
|
|
1158
1157
|
// todo should self.serverInfo.productUri match self.engine.buildInfo.productUri ?
|
|
1159
1158
|
for (const endpointOptions of endpointDefinitions) {
|
|
1160
1159
|
const endPoint = this.createEndpointDescriptions(options!, endpointOptions);
|
package/source/server_engine.ts
CHANGED
|
@@ -96,7 +96,6 @@ import { ServerSession } from "./server_session";
|
|
|
96
96
|
import { Subscription } from "./server_subscription";
|
|
97
97
|
import { sessionsCompatibleForTransfer } from "./sessions_compatible_for_transfer";
|
|
98
98
|
import { OPCUAServerOptions } from "./opcua_server";
|
|
99
|
-
import { IUserManager } from "node-opcua-address-space/source";
|
|
100
99
|
|
|
101
100
|
const debugLog = make_debugLog(__filename);
|
|
102
101
|
const errorLog = make_errorLog(__filename);
|
|
@@ -123,25 +122,13 @@ function setSubscriptionDurable(
|
|
|
123
122
|
) {
|
|
124
123
|
// see https://reference.opcfoundation.org/v104/Core/docs/Part5/9.3/
|
|
125
124
|
// https://reference.opcfoundation.org/v104/Core/docs/Part4/6.8/
|
|
126
|
-
assert(Array.isArray(inputArguments));
|
|
127
125
|
assert(typeof callback === "function");
|
|
128
|
-
assert(Object.prototype.hasOwnProperty.call(context, "session"), " expecting a session id in the context object");
|
|
129
|
-
const session = context.session as ServerSession;
|
|
130
|
-
if (!session) {
|
|
131
|
-
return callback(null, { statusCode: StatusCodes.BadInternalError });
|
|
132
|
-
}
|
|
133
|
-
const subscriptionId = inputArguments[0].value as UInt32;
|
|
134
|
-
const lifetimeInHours = inputArguments[1].value as UInt32;
|
|
135
126
|
|
|
136
|
-
const
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return callback(null, { statusCode: StatusCodes.BadUserAccessDenied });
|
|
142
|
-
}
|
|
143
|
-
return callback(null, { statusCode: StatusCodes.BadSubscriptionIdInvalid });
|
|
144
|
-
}
|
|
127
|
+
const data = _getSubscription.call(this, inputArguments, context);
|
|
128
|
+
if (data.statusCode) return callback(null, { statusCode: data.statusCode });
|
|
129
|
+
const { subscription } = data;
|
|
130
|
+
|
|
131
|
+
const lifetimeInHours = inputArguments[1].value as UInt32;
|
|
145
132
|
if (subscription.monitoredItemCount > 0) {
|
|
146
133
|
// This is returned when a Subscription already contains MonitoredItems.
|
|
147
134
|
return callback(null, { statusCode: StatusCodes.BadInvalidState });
|
|
@@ -199,8 +186,7 @@ function setSubscriptionDurable(
|
|
|
199
186
|
callback(null, callMethodResult);
|
|
200
187
|
}
|
|
201
188
|
|
|
202
|
-
|
|
203
|
-
function getMonitoredItemsId(
|
|
189
|
+
function requestServerStateChange(
|
|
204
190
|
this: ServerEngine,
|
|
205
191
|
inputArguments: Variant[],
|
|
206
192
|
context: ISessionContext,
|
|
@@ -209,23 +195,70 @@ function getMonitoredItemsId(
|
|
|
209
195
|
assert(Array.isArray(inputArguments));
|
|
210
196
|
assert(typeof callback === "function");
|
|
211
197
|
assert(Object.prototype.hasOwnProperty.call(context, "session"), " expecting a session id in the context object");
|
|
212
|
-
|
|
213
198
|
const session = context.session as ServerSession;
|
|
214
199
|
if (!session) {
|
|
215
200
|
return callback(null, { statusCode: StatusCodes.BadInternalError });
|
|
216
201
|
}
|
|
217
202
|
|
|
203
|
+
return callback(null, { statusCode: StatusCodes.BadNotImplemented });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function _getSubscription(
|
|
207
|
+
this: ServerEngine,
|
|
208
|
+
inputArguments: Variant[],
|
|
209
|
+
context: ISessionContext
|
|
210
|
+
): { subscription: Subscription, statusCode?: never } | { statusCode: StatusCode, subscription?: never } {
|
|
211
|
+
assert(Array.isArray(inputArguments));
|
|
212
|
+
assert(Object.prototype.hasOwnProperty.call(context, "session"), " expecting a session id in the context object");
|
|
213
|
+
const session = context.session as ServerSession;
|
|
214
|
+
if (!session) {
|
|
215
|
+
return { statusCode: StatusCodes.BadInternalError };
|
|
216
|
+
}
|
|
218
217
|
const subscriptionId = inputArguments[0].value;
|
|
219
218
|
const subscription = session.getSubscription(subscriptionId);
|
|
220
219
|
if (!subscription) {
|
|
221
220
|
// subscription may belongs to a different session that ours
|
|
222
221
|
if (this.findSubscription(subscriptionId)) {
|
|
223
222
|
// if yes, then access to Subscription data should be denied
|
|
224
|
-
return
|
|
223
|
+
return { statusCode: StatusCodes.BadUserAccessDenied };
|
|
225
224
|
}
|
|
226
|
-
|
|
227
|
-
return callback(null, { statusCode: StatusCodes.BadSubscriptionIdInvalid });
|
|
225
|
+
return { statusCode: StatusCodes.BadSubscriptionIdInvalid };
|
|
228
226
|
}
|
|
227
|
+
return { subscription };
|
|
228
|
+
|
|
229
|
+
}
|
|
230
|
+
function resendData(
|
|
231
|
+
this: ServerEngine,
|
|
232
|
+
inputArguments: Variant[],
|
|
233
|
+
context: ISessionContext,
|
|
234
|
+
callback: CallbackT<CallMethodResultOptions>
|
|
235
|
+
): void {
|
|
236
|
+
assert(typeof callback === "function");
|
|
237
|
+
|
|
238
|
+
const data = _getSubscription.call(this, inputArguments, context);
|
|
239
|
+
if (data.statusCode) return callback(null, { statusCode: data.statusCode });
|
|
240
|
+
const { subscription } = data;
|
|
241
|
+
|
|
242
|
+
subscription.resendInitialValues().then(() => {
|
|
243
|
+
callback(null, { statusCode: StatusCodes.Good });
|
|
244
|
+
}).catch((err) => callback(err));
|
|
245
|
+
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
// binding methods
|
|
250
|
+
function getMonitoredItemsId(
|
|
251
|
+
this: ServerEngine,
|
|
252
|
+
inputArguments: Variant[],
|
|
253
|
+
context: ISessionContext,
|
|
254
|
+
callback: CallbackT<CallMethodResultOptions>
|
|
255
|
+
) {
|
|
256
|
+
assert(typeof callback === "function");
|
|
257
|
+
|
|
258
|
+
const data = _getSubscription.call(this, inputArguments, context);
|
|
259
|
+
if (data.statusCode) return callback(null, { statusCode: data.statusCode });
|
|
260
|
+
const { subscription } = data;
|
|
261
|
+
|
|
229
262
|
const result = subscription.getMonitoredItems();
|
|
230
263
|
assert(result.statusCode);
|
|
231
264
|
assert(result.serverHandles.length === result.clientHandles.length);
|
|
@@ -1202,6 +1235,8 @@ export class ServerEngine extends EventEmitter {
|
|
|
1202
1235
|
|
|
1203
1236
|
this.__internal_bindMethod(makeNodeId(MethodIds.Server_GetMonitoredItems), getMonitoredItemsId.bind(this));
|
|
1204
1237
|
this.__internal_bindMethod(makeNodeId(MethodIds.Server_SetSubscriptionDurable), setSubscriptionDurable.bind(this));
|
|
1238
|
+
this.__internal_bindMethod(makeNodeId(MethodIds.Server_ResendData), resendData.bind(this));
|
|
1239
|
+
this.__internal_bindMethod(makeNodeId(MethodIds.Server_RequestServerStateChange), requestServerStateChange.bind(this));
|
|
1205
1240
|
|
|
1206
1241
|
// fix getMonitoredItems.outputArguments arrayDimensions
|
|
1207
1242
|
const fixGetMonitoredItemArgs = () => {
|
|
@@ -1415,7 +1450,7 @@ export class ServerEngine extends EventEmitter {
|
|
|
1415
1450
|
(!dataValue.serverTimestamp || dataValue.serverTimestamp.getTime() === minOPCUADate.getTime())
|
|
1416
1451
|
) {
|
|
1417
1452
|
dataValue.serverTimestamp = context.currentTime.timestamp;
|
|
1418
|
-
dataValue.
|
|
1453
|
+
dataValue.serverPicoseconds = 0; // context.currentTime.picoseconds;
|
|
1419
1454
|
}
|
|
1420
1455
|
dataValues.push(dataValue);
|
|
1421
1456
|
}
|
|
@@ -2246,8 +2281,8 @@ export class ServerEngine extends EventEmitter {
|
|
|
2246
2281
|
} else {
|
|
2247
2282
|
warningLog(
|
|
2248
2283
|
chalk.yellow("WARNING: cannot bind a method with id ") +
|
|
2249
|
-
|
|
2250
|
-
|
|
2284
|
+
chalk.cyan(nodeId.toString()) +
|
|
2285
|
+
chalk.yellow(". please check your nodeset.xml file or add this node programmatically")
|
|
2251
2286
|
);
|
|
2252
2287
|
warningLog(traceFromThisProjectOnly());
|
|
2253
2288
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { EventEmitter } from "events";
|
|
7
7
|
import * as chalk from "chalk";
|
|
8
8
|
|
|
9
|
-
import { SessionContext,AddressSpace, BaseNode, Duration, UAObjectType } from "node-opcua-address-space";
|
|
9
|
+
import { SessionContext, AddressSpace, BaseNode, Duration, UAObjectType } from "node-opcua-address-space";
|
|
10
10
|
import { assert } from "node-opcua-assert";
|
|
11
11
|
import { Byte, UInt32 } from "node-opcua-basic-types";
|
|
12
12
|
import { SubscriptionDiagnosticsDataType } from "node-opcua-common";
|
|
@@ -81,19 +81,22 @@ function _adjust_maxKeepAliveCount(maxKeepAliveCount?: number /*,publishingInter
|
|
|
81
81
|
return maxKeepAliveCount;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
const MaxUint32 = 0xFFFFFFFF;
|
|
85
|
+
|
|
84
86
|
function _adjust_lifeTimeCount(lifeTimeCount: number, maxKeepAliveCount: number, publishingInterval: number): number {
|
|
85
87
|
lifeTimeCount = lifeTimeCount || 1;
|
|
86
88
|
|
|
87
|
-
// let's make sure that lifeTimeCount is at least three time maxKeepAliveCount
|
|
88
|
-
// Note : the specs say ( part 3 - CreateSubscriptionParameter )
|
|
89
|
-
// "The lifetime count shall be a minimum of three times the keep keep-alive count."
|
|
90
|
-
lifeTimeCount = Math.max(lifeTimeCount, maxKeepAliveCount * 3);
|
|
91
|
-
|
|
92
89
|
const minTicks = Math.ceil(Subscription.minimumLifetimeDuration / publishingInterval);
|
|
93
90
|
const maxTicks = Math.floor(Subscription.maximumLifetimeDuration / publishingInterval);
|
|
94
91
|
|
|
95
92
|
lifeTimeCount = Math.max(minTicks, lifeTimeCount);
|
|
96
93
|
lifeTimeCount = Math.min(maxTicks, lifeTimeCount);
|
|
94
|
+
|
|
95
|
+
// let's make sure that lifeTimeCount is at least three time maxKeepAliveCount
|
|
96
|
+
// Note : the specs say ( part 3 - CreateSubscriptionParameter )
|
|
97
|
+
// "The lifetime count shall be a minimum of three times the keep keep-alive count."
|
|
98
|
+
lifeTimeCount = Math.max(lifeTimeCount, Math.min(maxKeepAliveCount * 3, MaxUint32));
|
|
99
|
+
|
|
97
100
|
return lifeTimeCount;
|
|
98
101
|
}
|
|
99
102
|
|
|
@@ -1362,7 +1365,7 @@ export class Subscription extends EventEmitter {
|
|
|
1362
1365
|
const availableSequenceNumbers = this.getAvailableSequenceNumbers();
|
|
1363
1366
|
assert(
|
|
1364
1367
|
!response.notificationMessage ||
|
|
1365
|
-
|
|
1368
|
+
availableSequenceNumbers[availableSequenceNumbers.length - 1] === response.notificationMessage.sequenceNumber
|
|
1366
1369
|
);
|
|
1367
1370
|
response.availableSequenceNumbers = availableSequenceNumbers;
|
|
1368
1371
|
|
|
@@ -1439,7 +1442,7 @@ export class Subscription extends EventEmitter {
|
|
|
1439
1442
|
} else {
|
|
1440
1443
|
debugLog(
|
|
1441
1444
|
" -> subscription.state === LATE , " +
|
|
1442
|
-
|
|
1445
|
+
"because keepAlive Response cannot be send due to lack of PublishRequest"
|
|
1443
1446
|
);
|
|
1444
1447
|
if (this.messageSent || this.keepAliveCounterHasExpired) {
|
|
1445
1448
|
this.state = SubscriptionState.LATE;
|