node-opcua-server 2.76.1 → 2.76.2
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.d.ts +110 -110
- package/dist/base_server.js +473 -473
- package/dist/factory.d.ts +12 -12
- package/dist/factory.js +23 -23
- package/dist/filter/check_where_clause_on_address_space.d.ts +3 -3
- package/dist/filter/check_where_clause_on_address_space.js +22 -22
- package/dist/filter/extract_event_fields.d.ts +10 -10
- package/dist/filter/extract_event_fields.js +17 -17
- package/dist/helper.d.ts +10 -10
- package/dist/helper.js +75 -75
- package/dist/history_server_capabilities.d.ts +35 -35
- package/dist/history_server_capabilities.js +43 -43
- package/dist/i_channel_data.d.ts +13 -13
- package/dist/i_channel_data.js +2 -2
- package/dist/i_register_server_manager.d.ts +16 -16
- package/dist/i_register_server_manager.js +2 -2
- package/dist/i_server_side_publish_engine.d.ts +36 -36
- package/dist/i_server_side_publish_engine.js +49 -49
- package/dist/i_socket_data.d.ts +11 -11
- package/dist/i_socket_data.js +2 -2
- package/dist/index.d.ts +16 -16
- package/dist/index.js +32 -32
- package/dist/monitored_item.d.ts +177 -177
- package/dist/monitored_item.js +1001 -1001
- package/dist/node_sampler.d.ts +3 -3
- package/dist/node_sampler.js +75 -75
- package/dist/opcua_server.d.ts +747 -747
- package/dist/opcua_server.js +2431 -2431
- package/dist/queue.d.ts +11 -11
- package/dist/queue.js +71 -71
- package/dist/register_server_manager.d.ts +96 -96
- package/dist/register_server_manager.js +584 -584
- package/dist/register_server_manager_hidden.d.ts +17 -17
- package/dist/register_server_manager_hidden.js +27 -27
- package/dist/register_server_manager_mdns_only.d.ts +22 -22
- package/dist/register_server_manager_mdns_only.js +55 -55
- package/dist/server_capabilities.d.ts +148 -148
- package/dist/server_capabilities.js +92 -92
- package/dist/server_end_point.d.ts +183 -183
- package/dist/server_end_point.js +817 -817
- package/dist/server_engine.d.ts +317 -317
- package/dist/server_engine.js +1716 -1716
- package/dist/server_publish_engine.d.ts +113 -113
- package/dist/server_publish_engine.js +541 -541
- package/dist/server_publish_engine_for_orphan_subscriptions.d.ts +16 -16
- package/dist/server_publish_engine_for_orphan_subscriptions.js +51 -51
- package/dist/server_session.d.ts +182 -182
- package/dist/server_session.js +739 -739
- package/dist/server_subscription.d.ts +421 -421
- package/dist/server_subscription.js +1346 -1346
- package/dist/sessions_compatible_for_transfer.d.ts +2 -2
- package/dist/sessions_compatible_for_transfer.js +39 -39
- package/dist/user_manager.d.ts +32 -32
- package/dist/user_manager.js +74 -74
- package/dist/user_manager_ua.d.ts +3 -3
- package/dist/user_manager_ua.js +39 -39
- package/dist/validate_filter.d.ts +5 -5
- package/dist/validate_filter.js +60 -60
- package/package.json +42 -41
package/dist/monitored_item.js
CHANGED
|
@@ -1,1002 +1,1002 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.MonitoredItem = void 0;
|
|
13
|
-
/**
|
|
14
|
-
* @module node-opcua-server
|
|
15
|
-
*/
|
|
16
|
-
const events_1 = require("events");
|
|
17
|
-
const chalk = require("chalk");
|
|
18
|
-
const node_opcua_assert_1 = require("node-opcua-assert");
|
|
19
|
-
const node_opcua_address_space_1 = require("node-opcua-address-space");
|
|
20
|
-
const node_opcua_service_filter_1 = require("node-opcua-service-filter");
|
|
21
|
-
const node_opcua_data_model_1 = require("node-opcua-data-model");
|
|
22
|
-
const node_opcua_data_model_2 = require("node-opcua-data-model");
|
|
23
|
-
const node_opcua_data_value_1 = require("node-opcua-data-value");
|
|
24
|
-
const node_opcua_debug_1 = require("node-opcua-debug");
|
|
25
|
-
const node_opcua_numeric_range_1 = require("node-opcua-numeric-range");
|
|
26
|
-
const node_opcua_object_registry_1 = require("node-opcua-object-registry");
|
|
27
|
-
const node_opcua_service_filter_2 = require("node-opcua-service-filter");
|
|
28
|
-
const node_opcua_service_read_1 = require("node-opcua-service-read");
|
|
29
|
-
const node_opcua_service_subscription_1 = require("node-opcua-service-subscription");
|
|
30
|
-
const node_opcua_service_subscription_2 = require("node-opcua-service-subscription");
|
|
31
|
-
const node_opcua_status_code_1 = require("node-opcua-status-code");
|
|
32
|
-
const node_opcua_types_1 = require("node-opcua-types");
|
|
33
|
-
const node_opcua_variant_1 = require("node-opcua-variant");
|
|
34
|
-
const node_sampler_1 = require("./node_sampler");
|
|
35
|
-
const validate_filter_1 = require("./validate_filter");
|
|
36
|
-
const check_where_clause_on_address_space_1 = require("./filter/check_where_clause_on_address_space");
|
|
37
|
-
const defaultItemToMonitor = new node_opcua_service_read_1.ReadValueId({
|
|
38
|
-
attributeId: node_opcua_data_model_2.AttributeIds.Value,
|
|
39
|
-
indexRange: undefined
|
|
40
|
-
});
|
|
41
|
-
const debugLog = (0, node_opcua_debug_1.make_debugLog)(__filename);
|
|
42
|
-
const doDebug = (0, node_opcua_debug_1.checkDebugFlag)(__filename);
|
|
43
|
-
const warningLog = (0, node_opcua_debug_1.make_warningLog)(__filename);
|
|
44
|
-
function _adjust_sampling_interval(samplingInterval, node_minimumSamplingInterval) {
|
|
45
|
-
(0, node_opcua_assert_1.assert)(typeof node_minimumSamplingInterval === "number", "expecting a number");
|
|
46
|
-
if (samplingInterval === 0) {
|
|
47
|
-
return node_minimumSamplingInterval === 0
|
|
48
|
-
? samplingInterval
|
|
49
|
-
: Math.max(MonitoredItem.minimumSamplingInterval, node_minimumSamplingInterval);
|
|
50
|
-
}
|
|
51
|
-
(0, node_opcua_assert_1.assert)(samplingInterval >= 0, " this case should have been prevented outside");
|
|
52
|
-
samplingInterval = samplingInterval || MonitoredItem.defaultSamplingInterval;
|
|
53
|
-
samplingInterval = Math.max(samplingInterval, MonitoredItem.minimumSamplingInterval);
|
|
54
|
-
samplingInterval = Math.min(samplingInterval, MonitoredItem.maximumSamplingInterval);
|
|
55
|
-
samplingInterval =
|
|
56
|
-
node_minimumSamplingInterval === 0 ? samplingInterval : Math.max(samplingInterval, node_minimumSamplingInterval);
|
|
57
|
-
return samplingInterval;
|
|
58
|
-
}
|
|
59
|
-
const maxQueueSize = 5000;
|
|
60
|
-
function _adjust_queue_size(queueSize) {
|
|
61
|
-
queueSize = Math.min(queueSize, maxQueueSize);
|
|
62
|
-
queueSize = Math.max(1, queueSize);
|
|
63
|
-
return queueSize;
|
|
64
|
-
}
|
|
65
|
-
function _validate_parameters(monitoringParameters) {
|
|
66
|
-
// xx assert(options instanceof MonitoringParameters);
|
|
67
|
-
(0, node_opcua_assert_1.assert)(Object.prototype.hasOwnProperty.call(monitoringParameters, "clientHandle"));
|
|
68
|
-
(0, node_opcua_assert_1.assert)(Object.prototype.hasOwnProperty.call(monitoringParameters, "samplingInterval"));
|
|
69
|
-
(0, node_opcua_assert_1.assert)(isFinite(monitoringParameters.clientHandle));
|
|
70
|
-
(0, node_opcua_assert_1.assert)(isFinite(monitoringParameters.samplingInterval));
|
|
71
|
-
(0, node_opcua_assert_1.assert)(typeof monitoringParameters.discardOldest === "boolean");
|
|
72
|
-
(0, node_opcua_assert_1.assert)(isFinite(monitoringParameters.queueSize));
|
|
73
|
-
(0, node_opcua_assert_1.assert)(monitoringParameters.queueSize >= 0);
|
|
74
|
-
}
|
|
75
|
-
function statusCodeHasChanged(newDataValue, oldDataValue) {
|
|
76
|
-
(0, node_opcua_assert_1.assert)(newDataValue instanceof node_opcua_data_value_1.DataValue);
|
|
77
|
-
(0, node_opcua_assert_1.assert)(oldDataValue instanceof node_opcua_data_value_1.DataValue);
|
|
78
|
-
return newDataValue.statusCode !== oldDataValue.statusCode;
|
|
79
|
-
}
|
|
80
|
-
function valueHasChanged(newDataValue, oldDataValue, deadbandType, deadbandValue) {
|
|
81
|
-
(0, node_opcua_assert_1.assert)(newDataValue instanceof node_opcua_data_value_1.DataValue);
|
|
82
|
-
(0, node_opcua_assert_1.assert)(oldDataValue instanceof node_opcua_data_value_1.DataValue);
|
|
83
|
-
switch (deadbandType) {
|
|
84
|
-
case node_opcua_service_subscription_2.DeadbandType.None:
|
|
85
|
-
(0, node_opcua_assert_1.assert)(newDataValue.value instanceof node_opcua_variant_1.Variant);
|
|
86
|
-
(0, node_opcua_assert_1.assert)(newDataValue.value instanceof node_opcua_variant_1.Variant);
|
|
87
|
-
// No Deadband calculation should be applied.
|
|
88
|
-
return (0, node_opcua_service_subscription_2.isOutsideDeadbandNone)(oldDataValue.value, newDataValue.value);
|
|
89
|
-
case node_opcua_service_subscription_2.DeadbandType.Absolute:
|
|
90
|
-
// AbsoluteDeadband
|
|
91
|
-
return (0, node_opcua_service_subscription_2.isOutsideDeadbandAbsolute)(oldDataValue.value, newDataValue.value, deadbandValue);
|
|
92
|
-
default: {
|
|
93
|
-
// Percent_2 PercentDeadband (This type is specified in Part 8).
|
|
94
|
-
(0, node_opcua_assert_1.assert)(deadbandType === node_opcua_service_subscription_2.DeadbandType.Percent);
|
|
95
|
-
// The range of the deadbandValue is from 0.0 to 100.0 Percent.
|
|
96
|
-
(0, node_opcua_assert_1.assert)(deadbandValue >= 0 && deadbandValue <= 100);
|
|
97
|
-
// DeadbandType = PercentDeadband
|
|
98
|
-
// For this type of deadband the deadbandValue is defined as the percentage of the EURange. That is,
|
|
99
|
-
// it applies only to AnalogItems with an EURange Property that defines the typical value range for the
|
|
100
|
-
// item. This range shall be multiplied with the deadbandValue and then compared to the actual value change
|
|
101
|
-
// to determine the need for a data change notification. The following pseudo code shows how the deadband
|
|
102
|
-
// is calculated:
|
|
103
|
-
// DataChange if (absolute value of (last cached value - current value) >
|
|
104
|
-
// (deadbandValue/100.0) * ((high-low) of EURange)))
|
|
105
|
-
//
|
|
106
|
-
// Specifying a deadbandValue outside of this range will be rejected and reported with the
|
|
107
|
-
// StatusCode BadDeadbandFilterInvalid (see Table 27).
|
|
108
|
-
// If the Value of the MonitoredItem is an array, then the deadband calculation logic shall be applied to
|
|
109
|
-
// each element of the array. If an element that requires a DataChange is found, then no further
|
|
110
|
-
// deadband checking is necessary and the entire array shall be returned.
|
|
111
|
-
(0, node_opcua_assert_1.assert)(this.node !== null, "expecting a valid address_space object here to get access the the EURange");
|
|
112
|
-
const euRangeNode = this.node.getChildByName("EURange");
|
|
113
|
-
if (euRangeNode && euRangeNode.nodeClass === node_opcua_data_model_1.NodeClass.Variable) {
|
|
114
|
-
// double,double
|
|
115
|
-
const rangeVariant = euRangeNode.readValue().value;
|
|
116
|
-
return (0, node_opcua_service_subscription_2.isOutsideDeadbandPercent)(oldDataValue.value, newDataValue.value, deadbandValue, rangeVariant.value);
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
console.log("EURange is not of type Variable");
|
|
120
|
-
}
|
|
121
|
-
return true;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
function timestampHasChanged(t1, t2) {
|
|
126
|
-
if (t1 || !t2 || t2 || !t1) {
|
|
127
|
-
return true;
|
|
128
|
-
}
|
|
129
|
-
if (!t1 || !t2) {
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
return t1.getTime() !== t2.getTime();
|
|
133
|
-
}
|
|
134
|
-
function isGoodish(statusCode) {
|
|
135
|
-
return statusCode.value < 0x10000000;
|
|
136
|
-
}
|
|
137
|
-
function apply_dataChange_filter(newDataValue, oldDataValue) {
|
|
138
|
-
var _a;
|
|
139
|
-
/* istanbul ignore next */
|
|
140
|
-
if (!this.filter || !(this.filter instanceof node_opcua_service_subscription_2.DataChangeFilter)) {
|
|
141
|
-
throw new Error("Internal Error");
|
|
142
|
-
}
|
|
143
|
-
const trigger = this.filter.trigger;
|
|
144
|
-
// istanbul ignore next
|
|
145
|
-
if (doDebug) {
|
|
146
|
-
try {
|
|
147
|
-
debugLog("filter pass ?", node_opcua_service_subscription_2.DataChangeTrigger[trigger], (_a = this.oldDataValue) === null || _a === void 0 ? void 0 : _a.toString(), newDataValue.toString());
|
|
148
|
-
if (trigger === node_opcua_service_subscription_2.DataChangeTrigger.Status ||
|
|
149
|
-
trigger === node_opcua_service_subscription_2.DataChangeTrigger.StatusValue ||
|
|
150
|
-
trigger === node_opcua_service_subscription_2.DataChangeTrigger.StatusValueTimestamp) {
|
|
151
|
-
debugLog("statusCodeHasChanged ", statusCodeHasChanged(newDataValue, oldDataValue));
|
|
152
|
-
}
|
|
153
|
-
if (trigger === node_opcua_service_subscription_2.DataChangeTrigger.StatusValue || trigger === node_opcua_service_subscription_2.DataChangeTrigger.StatusValueTimestamp) {
|
|
154
|
-
debugLog("valueHasChanged ", valueHasChanged.call(this, newDataValue, oldDataValue, this.filter.deadbandType, this.filter.deadbandValue));
|
|
155
|
-
}
|
|
156
|
-
if (trigger === node_opcua_service_subscription_2.DataChangeTrigger.StatusValueTimestamp) {
|
|
157
|
-
debugLog("timestampHasChanged ", timestampHasChanged(newDataValue.sourceTimestamp, oldDataValue.sourceTimestamp));
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
catch (err) {
|
|
161
|
-
warningLog(err);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
switch (trigger) {
|
|
165
|
-
case node_opcua_service_subscription_2.DataChangeTrigger.Status: {
|
|
166
|
-
//
|
|
167
|
-
// Status
|
|
168
|
-
// Report a notification ONLY if the StatusCode associated with
|
|
169
|
-
// the value changes. See Table 166 for StatusCodes defined in
|
|
170
|
-
// this standard. Part 8 specifies additional StatusCodes that are
|
|
171
|
-
// valid in particular for device data.
|
|
172
|
-
return statusCodeHasChanged(newDataValue, oldDataValue);
|
|
173
|
-
}
|
|
174
|
-
case node_opcua_service_subscription_2.DataChangeTrigger.StatusValue: {
|
|
175
|
-
// filtering value changes.
|
|
176
|
-
// change. The Deadband filter can be used in addition for
|
|
177
|
-
// Report a notification if either the StatusCode or the value
|
|
178
|
-
// StatusValue
|
|
179
|
-
// This is the default setting if no filter is set.
|
|
180
|
-
return (statusCodeHasChanged(newDataValue, oldDataValue) ||
|
|
181
|
-
valueHasChanged.call(this, newDataValue, oldDataValue, this.filter.deadbandType, this.filter.deadbandValue));
|
|
182
|
-
}
|
|
183
|
-
default: {
|
|
184
|
-
// StatusValueTimestamp
|
|
185
|
-
// Report a notification if either StatusCode, value or the
|
|
186
|
-
// SourceTimestamp change.
|
|
187
|
-
//
|
|
188
|
-
// If a Deadband filter is specified,this trigger has the same behavior as STATUS_VALUE_1.
|
|
189
|
-
//
|
|
190
|
-
// If the DataChangeFilter is not applied to the monitored item, STATUS_VALUE_1
|
|
191
|
-
// is the default reporting behavior
|
|
192
|
-
(0, node_opcua_assert_1.assert)(trigger === node_opcua_service_subscription_2.DataChangeTrigger.StatusValueTimestamp);
|
|
193
|
-
return (timestampHasChanged(newDataValue.sourceTimestamp, oldDataValue.sourceTimestamp) ||
|
|
194
|
-
statusCodeHasChanged(newDataValue, oldDataValue) ||
|
|
195
|
-
valueHasChanged.call(this, newDataValue, oldDataValue, this.filter.deadbandType, this.filter.deadbandValue));
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
function apply_filter(newDataValue) {
|
|
200
|
-
if (!this.oldDataValue) {
|
|
201
|
-
return true; // keep
|
|
202
|
-
}
|
|
203
|
-
if (this.filter instanceof node_opcua_service_subscription_2.DataChangeFilter) {
|
|
204
|
-
return apply_dataChange_filter.call(this, newDataValue, this.oldDataValue);
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
// if filter not set, by default report changes to Status or Value only
|
|
208
|
-
if (newDataValue.statusCode.value !== this.oldDataValue.statusCode.value) {
|
|
209
|
-
return true; // Keep because statusCode has changed ...
|
|
210
|
-
}
|
|
211
|
-
return !(0, node_opcua_variant_1.sameVariant)(newDataValue.value, this.oldDataValue.value);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
function setSemanticChangeBit(notification) {
|
|
215
|
-
if (notification instanceof node_opcua_service_subscription_1.MonitoredItemNotification) {
|
|
216
|
-
notification.value.statusCode = node_opcua_status_code_1.StatusCode.makeStatusCode(notification.value.statusCode || node_opcua_status_code_1.StatusCodes.Good, "SemanticChanged");
|
|
217
|
-
}
|
|
218
|
-
else if (notification instanceof node_opcua_data_value_1.DataValue) {
|
|
219
|
-
notification.statusCode = node_opcua_status_code_1.StatusCode.makeStatusCode(notification.statusCode || node_opcua_status_code_1.StatusCodes.Good, "SemanticChanged");
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
const useCommonTimer = true;
|
|
223
|
-
function isSourceNewerThan(a, b) {
|
|
224
|
-
var _a, _b;
|
|
225
|
-
if (!b) {
|
|
226
|
-
return true;
|
|
227
|
-
}
|
|
228
|
-
const at = ((_a = a.sourceTimestamp) === null || _a === void 0 ? void 0 : _a.getTime()) || 0;
|
|
229
|
-
const bt = ((_b = b.sourceTimestamp) === null || _b === void 0 ? void 0 : _b.getTime()) || 0;
|
|
230
|
-
if (at === bt) {
|
|
231
|
-
return a.sourcePicoseconds > b.sourcePicoseconds;
|
|
232
|
-
}
|
|
233
|
-
return at > bt;
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* a server side monitored item
|
|
237
|
-
*
|
|
238
|
-
* - Once created, the MonitoredItem will raised an "samplingEvent" event every "samplingInterval" millisecond
|
|
239
|
-
* until {{#crossLink "MonitoredItem/terminate:method"}}{{/crossLink}} is called.
|
|
240
|
-
*
|
|
241
|
-
* - It is up to the event receiver to call {{#crossLink "MonitoredItem/recordValue:method"}}{{/crossLink}}.
|
|
242
|
-
*
|
|
243
|
-
*/
|
|
244
|
-
class MonitoredItem extends events_1.EventEmitter {
|
|
245
|
-
constructor(options) {
|
|
246
|
-
super();
|
|
247
|
-
this.samplingInterval = -1;
|
|
248
|
-
this.discardOldest = true;
|
|
249
|
-
this.queueSize = 0;
|
|
250
|
-
this.samplingFunc = null;
|
|
251
|
-
this._is_sampling = false;
|
|
252
|
-
(0, node_opcua_assert_1.assert)(Object.prototype.hasOwnProperty.call(options, "monitoredItemId"));
|
|
253
|
-
(0, node_opcua_assert_1.assert)(!options.monitoringMode, "use setMonitoring mode explicitly to activate the monitored item");
|
|
254
|
-
options.itemToMonitor = options.itemToMonitor || defaultItemToMonitor;
|
|
255
|
-
this._samplingId = undefined;
|
|
256
|
-
this.clientHandle = 0; // invalid
|
|
257
|
-
this.filter = null;
|
|
258
|
-
this._set_parameters(options);
|
|
259
|
-
this.monitoredItemId = options.monitoredItemId; // ( known as serverHandle)
|
|
260
|
-
this.queue = [];
|
|
261
|
-
this.overflow = false;
|
|
262
|
-
this.oldDataValue = new node_opcua_data_value_1.DataValue({ statusCode: node_opcua_status_code_1.StatusCodes.BadDataUnavailable }); // unset initially
|
|
263
|
-
// user has to call setMonitoringMode
|
|
264
|
-
this.monitoringMode = node_opcua_service_subscription_1.MonitoringMode.Invalid;
|
|
265
|
-
this.timestampsToReturn = (0, node_opcua_data_value_1.coerceTimestampsToReturn)(options.timestampsToReturn);
|
|
266
|
-
this.itemToMonitor = options.itemToMonitor;
|
|
267
|
-
this._node = null;
|
|
268
|
-
this._semantic_version = 0;
|
|
269
|
-
if (doDebug) {
|
|
270
|
-
debugLog("Monitoring ", options.itemToMonitor.toString());
|
|
271
|
-
}
|
|
272
|
-
this._on_node_disposed_listener = null;
|
|
273
|
-
MonitoredItem.registry.register(this);
|
|
274
|
-
}
|
|
275
|
-
get node() {
|
|
276
|
-
return this._node;
|
|
277
|
-
}
|
|
278
|
-
set node(someNode) {
|
|
279
|
-
throw new Error("Unexpected way to set node");
|
|
280
|
-
}
|
|
281
|
-
setNode(node) {
|
|
282
|
-
(0, node_opcua_assert_1.assert)(!this.node || this.node === node, "node already set");
|
|
283
|
-
this._node = node;
|
|
284
|
-
this._semantic_version = node.semantic_version;
|
|
285
|
-
this._on_node_disposed_listener = () => this._on_node_disposed(this._node);
|
|
286
|
-
this._node.on("dispose", this._on_node_disposed_listener);
|
|
287
|
-
}
|
|
288
|
-
setMonitoringMode(monitoringMode) {
|
|
289
|
-
(0, node_opcua_assert_1.assert)(monitoringMode !== node_opcua_service_subscription_1.MonitoringMode.Invalid);
|
|
290
|
-
if (monitoringMode === this.monitoringMode) {
|
|
291
|
-
// nothing to do
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
const old_monitoringMode = this.monitoringMode;
|
|
295
|
-
this.monitoringMode = monitoringMode;
|
|
296
|
-
if (this.monitoringMode === node_opcua_service_subscription_1.MonitoringMode.Disabled) {
|
|
297
|
-
this._stop_sampling();
|
|
298
|
-
// OPCUA 1.03 part 4 : $5.12.4
|
|
299
|
-
// setting the mode to DISABLED causes all queued Notifications to be deleted
|
|
300
|
-
this._empty_queue();
|
|
301
|
-
}
|
|
302
|
-
else {
|
|
303
|
-
(0, node_opcua_assert_1.assert)(this.monitoringMode === node_opcua_service_subscription_1.MonitoringMode.Sampling || this.monitoringMode === node_opcua_service_subscription_1.MonitoringMode.Reporting);
|
|
304
|
-
// OPCUA 1.03 part 4 : $5.12.1.3
|
|
305
|
-
// When a MonitoredItem is enabled (i.e. when the MonitoringMode is changed from DISABLED to
|
|
306
|
-
// SAMPLING or REPORTING) or it is created in the enabled state, the Server shall report the first
|
|
307
|
-
// sample as soon as possible and the time of this sample becomes the starting point for the next
|
|
308
|
-
// sampling interval.
|
|
309
|
-
const recordInitialValue = old_monitoringMode === node_opcua_service_subscription_1.MonitoringMode.Invalid || old_monitoringMode === node_opcua_service_subscription_1.MonitoringMode.Disabled;
|
|
310
|
-
this._start_sampling(recordInitialValue);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
/**
|
|
314
|
-
* Terminate the MonitoredItem.
|
|
315
|
-
* @method terminate
|
|
316
|
-
*
|
|
317
|
-
* This will stop the internal sampling timer.
|
|
318
|
-
*/
|
|
319
|
-
terminate() {
|
|
320
|
-
this._stop_sampling();
|
|
321
|
-
}
|
|
322
|
-
dispose() {
|
|
323
|
-
if (doDebug) {
|
|
324
|
-
debugLog("DISPOSING MONITORED ITEM", this._node.nodeId.toString());
|
|
325
|
-
}
|
|
326
|
-
this._stop_sampling();
|
|
327
|
-
MonitoredItem.registry.unregister(this);
|
|
328
|
-
if (this._on_node_disposed_listener) {
|
|
329
|
-
this._node.removeListener("dispose", this._on_node_disposed_listener);
|
|
330
|
-
this._on_node_disposed_listener = null;
|
|
331
|
-
}
|
|
332
|
-
// x assert(this._samplingId === null,"Sampling Id must be null");
|
|
333
|
-
this.oldDataValue = undefined;
|
|
334
|
-
this.queue = [];
|
|
335
|
-
this.itemToMonitor = null;
|
|
336
|
-
this.filter = null;
|
|
337
|
-
this.monitoredItemId = 0;
|
|
338
|
-
this._node = null;
|
|
339
|
-
this._semantic_version = 0;
|
|
340
|
-
this.$subscription = undefined;
|
|
341
|
-
this.removeAllListeners();
|
|
342
|
-
(0, node_opcua_assert_1.assert)(!this._samplingId);
|
|
343
|
-
(0, node_opcua_assert_1.assert)(!this._value_changed_callback);
|
|
344
|
-
(0, node_opcua_assert_1.assert)(!this._semantic_changed_callback);
|
|
345
|
-
(0, node_opcua_assert_1.assert)(!this._attribute_changed_callback);
|
|
346
|
-
(0, node_opcua_assert_1.assert)(!this._on_opcua_event_received_callback);
|
|
347
|
-
this._on_opcua_event_received_callback = null;
|
|
348
|
-
this._attribute_changed_callback = null;
|
|
349
|
-
this._semantic_changed_callback = null;
|
|
350
|
-
this._on_opcua_event_received_callback = null;
|
|
351
|
-
}
|
|
352
|
-
get isSampling() {
|
|
353
|
-
return (!!this._samplingId ||
|
|
354
|
-
typeof this._value_changed_callback === "function" ||
|
|
355
|
-
typeof this._attribute_changed_callback === "function");
|
|
356
|
-
}
|
|
357
|
-
toString() {
|
|
358
|
-
var _a;
|
|
359
|
-
let str = "";
|
|
360
|
-
str += `monitored item nodeId : ${(_a = this.node) === null || _a === void 0 ? void 0 : _a.nodeId.toString()} \n`;
|
|
361
|
-
str += ` sampling interval : ${this.samplingInterval} \n`;
|
|
362
|
-
str += ` monitoredItemId : ${this.monitoredItemId} \n`;
|
|
363
|
-
return str;
|
|
364
|
-
}
|
|
365
|
-
/**
|
|
366
|
-
* @param dataValue the whole dataValue
|
|
367
|
-
* @param skipChangeTest indicates whether recordValue should not check that dataValue is really
|
|
368
|
-
* different from previous one, ( by checking timestamps but also variant value)
|
|
369
|
-
* @private
|
|
370
|
-
*
|
|
371
|
-
* Notes:
|
|
372
|
-
* - recordValue can only be called within timer event
|
|
373
|
-
* - for performance reason, dataValue may be a shared value with the underlying node,
|
|
374
|
-
* therefore recordValue must clone the dataValue to make sure it retains a snapshot
|
|
375
|
-
* of the contain at the time recordValue was called.
|
|
376
|
-
*
|
|
377
|
-
*/
|
|
378
|
-
recordValue(dataValue, skipChangeTest, indexRange) {
|
|
379
|
-
var _a;
|
|
380
|
-
(0, node_opcua_assert_1.assert)(dataValue instanceof node_opcua_data_value_1.DataValue);
|
|
381
|
-
(0, node_opcua_assert_1.assert)(dataValue !== this.oldDataValue, "recordValue expects different dataValue to be provided");
|
|
382
|
-
(0, node_opcua_assert_1.assert)(!dataValue.value || dataValue.value !== this.oldDataValue.value, "recordValue expects different dataValue.value to be provided");
|
|
383
|
-
(0, node_opcua_assert_1.assert)(!dataValue.value || dataValue.value.isValid(), "expecting a valid variant value");
|
|
384
|
-
const hasSemanticChanged = this.node && this.node.semantic_version !== this._semantic_version;
|
|
385
|
-
// xx console.log("`\n----------------------------",skipChangeTest,this.clientHandle,
|
|
386
|
-
// this.node.listenerCount("value_changed"),this.node.nodeId.toString());
|
|
387
|
-
// xx console.log("events ---- ",this.node.eventNames().join("-"));
|
|
388
|
-
// xx console.log("indexRange = ",indexRange ? indexRange.toString() :"");
|
|
389
|
-
// xx console.log("this.itemToMonitor.indexRange = ",this.itemToMonitor.indexRange.toString());
|
|
390
|
-
if (!hasSemanticChanged && indexRange && this.itemToMonitor.indexRange) {
|
|
391
|
-
// we just ignore changes that do not fall within our range
|
|
392
|
-
// ( unless semantic bit has changed )
|
|
393
|
-
if (!node_opcua_numeric_range_1.NumericRange.overlap(indexRange, this.itemToMonitor.indexRange)) {
|
|
394
|
-
return; // no overlap !
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
(0, node_opcua_assert_1.assert)(this.itemToMonitor, "must have a valid itemToMonitor(have this monitoredItem been disposed already ?");
|
|
398
|
-
// extract the range that we are interested with
|
|
399
|
-
dataValue = (0, node_opcua_data_value_1.extractRange)(dataValue, this.itemToMonitor.indexRange);
|
|
400
|
-
// istanbul ignore next
|
|
401
|
-
if (doDebug) {
|
|
402
|
-
debugLog("MonitoredItem#recordValue", this.node.nodeId.toString(), this.node.browseName.toString(), " has Changed = ", !(0, node_opcua_data_value_1.sameDataValue)(dataValue, this.oldDataValue), "skipChangeTest = ", skipChangeTest, "hasSemanticChanged = ", hasSemanticChanged);
|
|
403
|
-
}
|
|
404
|
-
// if semantic has changed, value need to be enqueued regardless of other assumptions
|
|
405
|
-
if (hasSemanticChanged) {
|
|
406
|
-
debugLog("_enqueue_value => because hasSemanticChanged");
|
|
407
|
-
setSemanticChangeBit(dataValue);
|
|
408
|
-
this._semantic_version = this.node.semantic_version;
|
|
409
|
-
return this._enqueue_value(dataValue);
|
|
410
|
-
}
|
|
411
|
-
const useIndexRange = this.itemToMonitor.indexRange && !this.itemToMonitor.indexRange.isEmpty();
|
|
412
|
-
if (!skipChangeTest) {
|
|
413
|
-
const hasChanged = !(0, node_opcua_data_value_1.sameDataValue)(dataValue, this.oldDataValue);
|
|
414
|
-
if (!hasChanged) {
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
if (!apply_filter.call(this, dataValue)) {
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
if (useIndexRange) {
|
|
422
|
-
// when an indexRange is provided , make sure that no record happens unless
|
|
423
|
-
// extracted variant in the selected range has really changed.
|
|
424
|
-
// istanbul ignore next
|
|
425
|
-
if (doDebug) {
|
|
426
|
-
debugLog("Current : ", this.oldDataValue.toString());
|
|
427
|
-
debugLog("New : ", dataValue.toString());
|
|
428
|
-
debugLog("indexRange=", indexRange);
|
|
429
|
-
}
|
|
430
|
-
if ((0, node_opcua_variant_1.sameVariant)(dataValue.value, this.oldDataValue.value)) {
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
// processTriggerItems
|
|
435
|
-
this.triggerLinkedItems();
|
|
436
|
-
if (doDebug) {
|
|
437
|
-
debugLog("RECORD VALUE ", (_a = this.node) === null || _a === void 0 ? void 0 : _a.nodeId.toString());
|
|
438
|
-
}
|
|
439
|
-
// store last value
|
|
440
|
-
this._enqueue_value(dataValue);
|
|
441
|
-
}
|
|
442
|
-
hasLinkItem(linkedMonitoredItemId) {
|
|
443
|
-
if (!this._linkedItems) {
|
|
444
|
-
return false;
|
|
445
|
-
}
|
|
446
|
-
return this._linkedItems.findIndex((x) => x === linkedMonitoredItemId) > 0;
|
|
447
|
-
}
|
|
448
|
-
addLinkItem(linkedMonitoredItemId) {
|
|
449
|
-
if (linkedMonitoredItemId === this.monitoredItemId) {
|
|
450
|
-
return node_opcua_status_code_1.StatusCodes.BadMonitoredItemIdInvalid;
|
|
451
|
-
}
|
|
452
|
-
this._linkedItems = this._linkedItems || [];
|
|
453
|
-
if (this.hasLinkItem(linkedMonitoredItemId)) {
|
|
454
|
-
return node_opcua_status_code_1.StatusCodes.BadMonitoredItemIdInvalid; // nothing to do
|
|
455
|
-
}
|
|
456
|
-
this._linkedItems.push(linkedMonitoredItemId);
|
|
457
|
-
return node_opcua_status_code_1.StatusCodes.Good;
|
|
458
|
-
}
|
|
459
|
-
removeLinkItem(linkedMonitoredItemId) {
|
|
460
|
-
if (!this._linkedItems || linkedMonitoredItemId === this.monitoredItemId) {
|
|
461
|
-
return node_opcua_status_code_1.StatusCodes.BadMonitoredItemIdInvalid;
|
|
462
|
-
}
|
|
463
|
-
const index = this._linkedItems.findIndex((x) => x === linkedMonitoredItemId);
|
|
464
|
-
if (index === -1) {
|
|
465
|
-
return node_opcua_status_code_1.StatusCodes.BadMonitoredItemIdInvalid;
|
|
466
|
-
}
|
|
467
|
-
this._linkedItems.splice(index, 1);
|
|
468
|
-
return node_opcua_status_code_1.StatusCodes.Good;
|
|
469
|
-
}
|
|
470
|
-
/**
|
|
471
|
-
* @internals
|
|
472
|
-
*/
|
|
473
|
-
triggerLinkedItems() {
|
|
474
|
-
var _a, _b;
|
|
475
|
-
if (!this.$subscription || !this._linkedItems) {
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
// see https://reference.opcfoundation.org/v104/Core/docs/Part4/5.12.1/#5.12.1.6
|
|
479
|
-
for (const linkItem of this._linkedItems) {
|
|
480
|
-
const linkedMonitoredItem = this.$subscription.getMonitoredItem(linkItem);
|
|
481
|
-
if (!linkedMonitoredItem) {
|
|
482
|
-
// monitoredItem may have been deleted
|
|
483
|
-
continue;
|
|
484
|
-
}
|
|
485
|
-
if (linkedMonitoredItem.monitoringMode === node_opcua_service_subscription_1.MonitoringMode.Disabled) {
|
|
486
|
-
continue;
|
|
487
|
-
}
|
|
488
|
-
if (linkedMonitoredItem.monitoringMode === node_opcua_service_subscription_1.MonitoringMode.Reporting) {
|
|
489
|
-
continue;
|
|
490
|
-
}
|
|
491
|
-
(0, node_opcua_assert_1.assert)(linkedMonitoredItem.monitoringMode === node_opcua_service_subscription_1.MonitoringMode.Sampling);
|
|
492
|
-
// istanbul ignore next
|
|
493
|
-
if (doDebug) {
|
|
494
|
-
debugLog("triggerLinkedItems => ", (_a = this.node) === null || _a === void 0 ? void 0 : _a.nodeId.toString(), (_b = linkedMonitoredItem.node) === null || _b === void 0 ? void 0 : _b.nodeId.toString());
|
|
495
|
-
}
|
|
496
|
-
linkedMonitoredItem.trigger();
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
get hasMonitoredItemNotifications() {
|
|
500
|
-
return this.queue.length > 0 || (this._triggeredNotifications !== undefined && this._triggeredNotifications.length > 0);
|
|
501
|
-
}
|
|
502
|
-
/**
|
|
503
|
-
* @internals
|
|
504
|
-
*/
|
|
505
|
-
trigger() {
|
|
506
|
-
setImmediate(() => {
|
|
507
|
-
this._triggeredNotifications = this._triggeredNotifications || [];
|
|
508
|
-
const notifications = this.extractMonitoredItemNotifications(true);
|
|
509
|
-
this._triggeredNotifications = [].concat(this._triggeredNotifications, notifications);
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
extractMonitoredItemNotifications(bForce = false) {
|
|
513
|
-
if (!bForce && this.monitoringMode === node_opcua_service_subscription_1.MonitoringMode.Sampling && this._triggeredNotifications) {
|
|
514
|
-
const notifications1 = this._triggeredNotifications;
|
|
515
|
-
this._triggeredNotifications = undefined;
|
|
516
|
-
return notifications1;
|
|
517
|
-
}
|
|
518
|
-
if (!bForce && this.monitoringMode !== node_opcua_service_subscription_1.MonitoringMode.Reporting) {
|
|
519
|
-
return [];
|
|
520
|
-
}
|
|
521
|
-
const notifications = this.queue;
|
|
522
|
-
this._empty_queue();
|
|
523
|
-
// apply semantic changed bit if necessary
|
|
524
|
-
if (notifications.length > 0 && this.node && this._semantic_version < this.node.semantic_version) {
|
|
525
|
-
const dataValue = notifications[notifications.length - 1];
|
|
526
|
-
setSemanticChangeBit(dataValue);
|
|
527
|
-
(0, node_opcua_assert_1.assert)(this.node.nodeClass === node_opcua_data_model_1.NodeClass.Variable);
|
|
528
|
-
this._semantic_version = this.node.semantic_version;
|
|
529
|
-
}
|
|
530
|
-
return notifications;
|
|
531
|
-
}
|
|
532
|
-
modify(timestampsToReturn, monitoringParameters) {
|
|
533
|
-
(0, node_opcua_assert_1.assert)(monitoringParameters instanceof node_opcua_service_subscription_1.MonitoringParameters);
|
|
534
|
-
const old_samplingInterval = this.samplingInterval;
|
|
535
|
-
this.timestampsToReturn = timestampsToReturn || this.timestampsToReturn;
|
|
536
|
-
if (old_samplingInterval !== 0 && monitoringParameters.samplingInterval === 0) {
|
|
537
|
-
monitoringParameters.samplingInterval = MonitoredItem.minimumSamplingInterval; // fastest possible
|
|
538
|
-
}
|
|
539
|
-
// spec says: Illegal request values for parameters that can be revised do not generate errors. Instead the
|
|
540
|
-
// server will choose default values and indicate them in the corresponding revised parameter
|
|
541
|
-
this._set_parameters(monitoringParameters);
|
|
542
|
-
this._adjust_queue_to_match_new_queue_size();
|
|
543
|
-
this._adjust_sampling(old_samplingInterval);
|
|
544
|
-
if (monitoringParameters.filter) {
|
|
545
|
-
const statusCodeFilter = (0, validate_filter_1.validateFilter)(monitoringParameters.filter, this.itemToMonitor, this.node);
|
|
546
|
-
if (statusCodeFilter.isNot(node_opcua_status_code_1.StatusCodes.Good)) {
|
|
547
|
-
return new node_opcua_service_subscription_1.MonitoredItemModifyResult({
|
|
548
|
-
statusCode: statusCodeFilter
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
// validate filter
|
|
553
|
-
// note : The DataChangeFilter does not have an associated result structure.
|
|
554
|
-
const filterResult = null; // new subscription_service.DataChangeFilter
|
|
555
|
-
return new node_opcua_service_subscription_1.MonitoredItemModifyResult({
|
|
556
|
-
filterResult,
|
|
557
|
-
revisedQueueSize: this.queueSize,
|
|
558
|
-
revisedSamplingInterval: this.samplingInterval,
|
|
559
|
-
statusCode: node_opcua_status_code_1.StatusCodes.Good
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
resendInitialValues() {
|
|
563
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
564
|
-
// tte first Publish response(s) after the TransferSubscriptions call shall contain the current values of all
|
|
565
|
-
// Monitored Items in the Subscription where the Monitoring Mode is set to Reporting.
|
|
566
|
-
// the first Publish response after the TransferSubscriptions call shall contain only the value changes since
|
|
567
|
-
// the last Publish response was sent.
|
|
568
|
-
// This parameter only applies to MonitoredItems used for monitoring Attribute changes.
|
|
569
|
-
this._stop_sampling();
|
|
570
|
-
return this._start_sampling(true);
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
|
-
/**
|
|
574
|
-
* @method _on_sampling_timer
|
|
575
|
-
* @private
|
|
576
|
-
* request
|
|
577
|
-
*
|
|
578
|
-
*/
|
|
579
|
-
_on_sampling_timer() {
|
|
580
|
-
// istanbul ignore next
|
|
581
|
-
if (doDebug) {
|
|
582
|
-
debugLog("MonitoredItem#_on_sampling_timer", this.node ? this.node.nodeId.toString() : "null", " isSampling?=", this._is_sampling);
|
|
583
|
-
}
|
|
584
|
-
if (this._samplingId) {
|
|
585
|
-
(0, node_opcua_assert_1.assert)(this.monitoringMode === node_opcua_service_subscription_1.MonitoringMode.Sampling || this.monitoringMode === node_opcua_service_subscription_1.MonitoringMode.Reporting);
|
|
586
|
-
if (this._is_sampling) {
|
|
587
|
-
// previous sampling call is not yet completed..
|
|
588
|
-
// there is nothing we can do about it except waiting until next tick.
|
|
589
|
-
// note : see also issue #156 on github
|
|
590
|
-
return;
|
|
591
|
-
}
|
|
592
|
-
// xx console.log("xxxx ON SAMPLING");
|
|
593
|
-
(0, node_opcua_assert_1.assert)(!this._is_sampling, "sampling func shall not be re-entrant !! fix it");
|
|
594
|
-
if (!this.samplingFunc) {
|
|
595
|
-
throw new Error("internal error : missing samplingFunc");
|
|
596
|
-
}
|
|
597
|
-
this._is_sampling = true;
|
|
598
|
-
this.samplingFunc.call(this, this.oldDataValue, (err, newDataValue) => {
|
|
599
|
-
if (!this._samplingId) {
|
|
600
|
-
// item has been disposed. The monitored item has been disposed while the async sampling func
|
|
601
|
-
// was taking place ... just ignore this
|
|
602
|
-
return;
|
|
603
|
-
}
|
|
604
|
-
if (err) {
|
|
605
|
-
console.log(" SAMPLING ERROR =>", err);
|
|
606
|
-
}
|
|
607
|
-
else {
|
|
608
|
-
// only record value if source timestamp is newer
|
|
609
|
-
// xx if (newDataValue && isSourceNewerThan(newDataValue, this.oldDataValue)) {
|
|
610
|
-
this._on_value_changed(newDataValue);
|
|
611
|
-
// xx }
|
|
612
|
-
}
|
|
613
|
-
this._is_sampling = false;
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
else {
|
|
617
|
-
/* istanbul ignore next */
|
|
618
|
-
debugLog("_on_sampling_timer call but MonitoredItem has been terminated !!! ");
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
_stop_sampling() {
|
|
622
|
-
// debugLog("MonitoredItem#_stop_sampling");
|
|
623
|
-
/* istanbul ignore next */
|
|
624
|
-
if (!this.node) {
|
|
625
|
-
throw new Error("Internal Error");
|
|
626
|
-
}
|
|
627
|
-
if (this._on_opcua_event_received_callback) {
|
|
628
|
-
(0, node_opcua_assert_1.assert)(typeof this._on_opcua_event_received_callback === "function");
|
|
629
|
-
this.node.removeListener("event", this._on_opcua_event_received_callback);
|
|
630
|
-
this._on_opcua_event_received_callback = null;
|
|
631
|
-
}
|
|
632
|
-
if (this._attribute_changed_callback) {
|
|
633
|
-
(0, node_opcua_assert_1.assert)(typeof this._attribute_changed_callback === "function");
|
|
634
|
-
const event_name = (0, node_opcua_address_space_1.makeAttributeEventName)(this.itemToMonitor.attributeId);
|
|
635
|
-
this.node.removeListener(event_name, this._attribute_changed_callback);
|
|
636
|
-
this._attribute_changed_callback = null;
|
|
637
|
-
}
|
|
638
|
-
if (this._value_changed_callback) {
|
|
639
|
-
// samplingInterval was 0 for a exception-based data Item
|
|
640
|
-
// we setup a event listener that we need to unwind here
|
|
641
|
-
(0, node_opcua_assert_1.assert)(typeof this._value_changed_callback === "function");
|
|
642
|
-
(0, node_opcua_assert_1.assert)(!this._samplingId);
|
|
643
|
-
this.node.removeListener("value_changed", this._value_changed_callback);
|
|
644
|
-
this._value_changed_callback = null;
|
|
645
|
-
}
|
|
646
|
-
if (this._semantic_changed_callback) {
|
|
647
|
-
(0, node_opcua_assert_1.assert)(typeof this._semantic_changed_callback === "function");
|
|
648
|
-
(0, node_opcua_assert_1.assert)(!this._samplingId);
|
|
649
|
-
this.node.removeListener("semantic_changed", this._semantic_changed_callback);
|
|
650
|
-
this._semantic_changed_callback = null;
|
|
651
|
-
}
|
|
652
|
-
if (this._samplingId) {
|
|
653
|
-
this._clear_timer();
|
|
654
|
-
}
|
|
655
|
-
(0, node_opcua_assert_1.assert)(!this._samplingId);
|
|
656
|
-
(0, node_opcua_assert_1.assert)(!this._value_changed_callback);
|
|
657
|
-
(0, node_opcua_assert_1.assert)(!this._semantic_changed_callback);
|
|
658
|
-
(0, node_opcua_assert_1.assert)(!this._attribute_changed_callback);
|
|
659
|
-
(0, node_opcua_assert_1.assert)(!this._on_opcua_event_received_callback);
|
|
660
|
-
}
|
|
661
|
-
_on_value_changed(dataValue, indexRange) {
|
|
662
|
-
(0, node_opcua_assert_1.assert)(dataValue instanceof node_opcua_data_value_1.DataValue);
|
|
663
|
-
this.recordValue(dataValue, false, indexRange);
|
|
664
|
-
}
|
|
665
|
-
_on_semantic_changed() {
|
|
666
|
-
const dataValue = this.node.readValue();
|
|
667
|
-
this._on_value_changed(dataValue);
|
|
668
|
-
}
|
|
669
|
-
_on_opcua_event(eventData) {
|
|
670
|
-
// TO DO : => Improve Filtering, bearing in mind that ....
|
|
671
|
-
// Release 1.04 8 OPC Unified Architecture, Part 9
|
|
672
|
-
// 4.5 Condition state synchronization
|
|
673
|
-
// To ensure a Client is always informed, the three special EventTypes
|
|
674
|
-
// (RefreshEndEventType, RefreshStartEventType and RefreshRequiredEventType)
|
|
675
|
-
// ignore the Event content filtering associated with a Subscription and will always be
|
|
676
|
-
// delivered to the Client.
|
|
677
|
-
var _a;
|
|
678
|
-
// istanbul ignore next
|
|
679
|
-
if (!this.filter || !(this.filter instanceof node_opcua_service_filter_2.EventFilter)) {
|
|
680
|
-
throw new Error("Internal Error : a EventFilter is requested");
|
|
681
|
-
}
|
|
682
|
-
const addressSpace = (_a = eventData.$eventDataSource) === null || _a === void 0 ? void 0 : _a.addressSpace;
|
|
683
|
-
if (!(0, check_where_clause_on_address_space_1.checkWhereClauseOnAdressSpace)(addressSpace, node_opcua_address_space_1.SessionContext.defaultContext, this.filter.whereClause, eventData)) {
|
|
684
|
-
return;
|
|
685
|
-
}
|
|
686
|
-
const selectClauses = this.filter.selectClauses ? this.filter.selectClauses : [];
|
|
687
|
-
const eventFields = (0, node_opcua_service_filter_1.extractEventFields)(node_opcua_address_space_1.SessionContext.defaultContext, selectClauses, eventData);
|
|
688
|
-
// istanbul ignore next
|
|
689
|
-
if (doDebug) {
|
|
690
|
-
console.log(" RECEIVED INTERNAL EVENT THAT WE ARE MONITORING");
|
|
691
|
-
console.log(this.filter ? this.filter.toString() : "no filter");
|
|
692
|
-
eventFields.forEach((e) => {
|
|
693
|
-
console.log(e.toString());
|
|
694
|
-
});
|
|
695
|
-
}
|
|
696
|
-
this._enqueue_event(eventFields);
|
|
697
|
-
}
|
|
698
|
-
_getSession() {
|
|
699
|
-
if (!this.$subscription) {
|
|
700
|
-
return null;
|
|
701
|
-
}
|
|
702
|
-
if (!this.$subscription.$session) {
|
|
703
|
-
return null;
|
|
704
|
-
}
|
|
705
|
-
return this.$subscription.$session;
|
|
706
|
-
}
|
|
707
|
-
_start_sampling(recordInitialValue) {
|
|
708
|
-
// istanbul ignore next
|
|
709
|
-
if (!this.node) {
|
|
710
|
-
throw new Error("Internal Error");
|
|
711
|
-
}
|
|
712
|
-
setImmediate(() => this.__start_sampling(recordInitialValue));
|
|
713
|
-
}
|
|
714
|
-
__start_sampling(recordInitialValue) {
|
|
715
|
-
// istanbul ignore next
|
|
716
|
-
if (!this.node) {
|
|
717
|
-
return; // we just want to ignore here ...
|
|
718
|
-
}
|
|
719
|
-
// make sure oldDataValue is scrapped so first data recording can happen
|
|
720
|
-
this.oldDataValue = new node_opcua_data_value_1.DataValue({ statusCode: node_opcua_status_code_1.StatusCodes.BadDataUnavailable }); // unset initially
|
|
721
|
-
this._stop_sampling();
|
|
722
|
-
const context = new node_opcua_address_space_1.SessionContext({
|
|
723
|
-
session: this._getSession()
|
|
724
|
-
});
|
|
725
|
-
if (this.itemToMonitor.attributeId === node_opcua_data_model_2.AttributeIds.EventNotifier) {
|
|
726
|
-
// istanbul ignore next
|
|
727
|
-
if (doDebug) {
|
|
728
|
-
debugLog("xxxxxx monitoring EventNotifier on", this.node.nodeId.toString(), this.node.browseName.toString());
|
|
729
|
-
}
|
|
730
|
-
// we are monitoring OPCUA Event
|
|
731
|
-
this._on_opcua_event_received_callback = this._on_opcua_event.bind(this);
|
|
732
|
-
this.node.on("event", this._on_opcua_event_received_callback);
|
|
733
|
-
return;
|
|
734
|
-
}
|
|
735
|
-
if (this.itemToMonitor.attributeId !== node_opcua_data_model_2.AttributeIds.Value) {
|
|
736
|
-
// sampling interval only applies to Value Attributes.
|
|
737
|
-
this.samplingInterval = 0; // turned to exception-based regardless of requested sampling interval
|
|
738
|
-
// non value attribute only react on value change
|
|
739
|
-
this._attribute_changed_callback = this._on_value_changed.bind(this);
|
|
740
|
-
const event_name = (0, node_opcua_address_space_1.makeAttributeEventName)(this.itemToMonitor.attributeId);
|
|
741
|
-
this.node.on(event_name, this._attribute_changed_callback);
|
|
742
|
-
if (recordInitialValue) {
|
|
743
|
-
// read initial value
|
|
744
|
-
const dataValue = this.node.readAttribute(context, this.itemToMonitor.attributeId);
|
|
745
|
-
this.recordValue(dataValue, true);
|
|
746
|
-
}
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
if (this.samplingInterval === 0) {
|
|
750
|
-
// we have a exception-based dataItem : event based model, so we do not need a timer
|
|
751
|
-
// rather , we setup the "value_changed_event";
|
|
752
|
-
this._value_changed_callback = this._on_value_changed.bind(this);
|
|
753
|
-
this._semantic_changed_callback = this._on_semantic_changed.bind(this);
|
|
754
|
-
this.node.on("value_changed", this._value_changed_callback);
|
|
755
|
-
this.node.on("semantic_changed", this._semantic_changed_callback);
|
|
756
|
-
// initiate first read
|
|
757
|
-
if (recordInitialValue) {
|
|
758
|
-
/* await */ new Promise((resolve) => {
|
|
759
|
-
this.node.readValueAsync(context, (err, dataValue) => {
|
|
760
|
-
if (!err && dataValue) {
|
|
761
|
-
this.recordValue(dataValue, true);
|
|
762
|
-
}
|
|
763
|
-
resolve();
|
|
764
|
-
});
|
|
765
|
-
});
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
else {
|
|
769
|
-
this._set_timer();
|
|
770
|
-
if (recordInitialValue) {
|
|
771
|
-
setImmediate(() => {
|
|
772
|
-
this._on_sampling_timer();
|
|
773
|
-
});
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
_set_parameters(monitoredParameters) {
|
|
778
|
-
_validate_parameters(monitoredParameters);
|
|
779
|
-
// only change clientHandle if it is valid (0<X<MAX)
|
|
780
|
-
if (monitoredParameters.clientHandle !== 0 && monitoredParameters.clientHandle !== 4294967295) {
|
|
781
|
-
this.clientHandle = monitoredParameters.clientHandle;
|
|
782
|
-
}
|
|
783
|
-
// The Server may support data that is collected based on a sampling model or generated based on an
|
|
784
|
-
// exception-based model. The fastest supported sampling interval may be equal to 0, which indicates
|
|
785
|
-
// that the data item is exception-based rather than being sampled at some period. An exception-based
|
|
786
|
-
// model means that the underlying system does not require sampling and reports data changes.
|
|
787
|
-
if (this.node && this.node.nodeClass === node_opcua_data_model_1.NodeClass.Variable) {
|
|
788
|
-
this.samplingInterval = _adjust_sampling_interval(monitoredParameters.samplingInterval, this.node ? this.node.minimumSamplingInterval : 0);
|
|
789
|
-
}
|
|
790
|
-
else {
|
|
791
|
-
this.samplingInterval = _adjust_sampling_interval(monitoredParameters.samplingInterval, 0);
|
|
792
|
-
}
|
|
793
|
-
this.discardOldest = monitoredParameters.discardOldest;
|
|
794
|
-
this.queueSize = _adjust_queue_size(monitoredParameters.queueSize);
|
|
795
|
-
// change filter
|
|
796
|
-
this.filter = monitoredParameters.filter || null;
|
|
797
|
-
}
|
|
798
|
-
_setOverflowBit(notification) {
|
|
799
|
-
if (Object.prototype.hasOwnProperty.call(notification, "value")) {
|
|
800
|
-
(0, node_opcua_assert_1.assert)(notification.value.statusCode.equals(node_opcua_status_code_1.StatusCodes.Good));
|
|
801
|
-
notification.value.statusCode = node_opcua_status_code_1.StatusCode.makeStatusCode(notification.value.statusCode, "Overflow | InfoTypeDataValue");
|
|
802
|
-
(0, node_opcua_assert_1.assert)((0, node_opcua_data_value_1.sameStatusCode)(notification.value.statusCode, node_opcua_status_code_1.StatusCodes.GoodWithOverflowBit));
|
|
803
|
-
(0, node_opcua_assert_1.assert)(notification.value.statusCode.hasOverflowBit);
|
|
804
|
-
}
|
|
805
|
-
// console.log(chalk.cyan("Setting Over"), !!this.$subscription, !!this.$subscription!.subscriptionDiagnostics);
|
|
806
|
-
if (this.$subscription && this.$subscription.subscriptionDiagnostics) {
|
|
807
|
-
this.$subscription.subscriptionDiagnostics.monitoringQueueOverflowCount++;
|
|
808
|
-
}
|
|
809
|
-
// to do eventQueueOverFlowCount
|
|
810
|
-
}
|
|
811
|
-
_enqueue_notification(notification) {
|
|
812
|
-
if (this.queueSize === 1) {
|
|
813
|
-
// https://reference.opcfoundation.org/v104/Core/docs/Part4/5.12.1/#5.12.1.5
|
|
814
|
-
// If the queue size is one, the queue becomes a buffer that always contains the newest
|
|
815
|
-
// Notification. In this case, if the sampling interval of the MonitoredItem is faster
|
|
816
|
-
// than the publishing interval of the Subscription, the MonitoredItem will be over
|
|
817
|
-
// sampling and the Client will always receive the most up-to-date value.
|
|
818
|
-
// The discard policy is ignored if the queue size is one.
|
|
819
|
-
// ensure queue size
|
|
820
|
-
if (!this.queue || this.queue.length !== 1) {
|
|
821
|
-
this.queue = [];
|
|
822
|
-
}
|
|
823
|
-
this.queue[0] = notification;
|
|
824
|
-
(0, node_opcua_assert_1.assert)(this.queue.length === 1);
|
|
825
|
-
}
|
|
826
|
-
else {
|
|
827
|
-
if (this.discardOldest) {
|
|
828
|
-
// push new value to queue
|
|
829
|
-
this.queue.push(notification);
|
|
830
|
-
if (this.queue.length > this.queueSize) {
|
|
831
|
-
this.overflow = true;
|
|
832
|
-
this.queue.shift(); // remove front element
|
|
833
|
-
// set overflow bit
|
|
834
|
-
this._setOverflowBit(this.queue[0]);
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
else {
|
|
838
|
-
if (this.queue.length < this.queueSize) {
|
|
839
|
-
this.queue.push(notification);
|
|
840
|
-
}
|
|
841
|
-
else {
|
|
842
|
-
this.overflow = true;
|
|
843
|
-
this._setOverflowBit(notification);
|
|
844
|
-
this.queue[this.queue.length - 1] = notification;
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
(0, node_opcua_assert_1.assert)(this.queue.length >= 1);
|
|
849
|
-
}
|
|
850
|
-
_makeDataChangeNotification(dataValue) {
|
|
851
|
-
if (this.clientHandle === -1 || this.clientHandle === 4294967295) {
|
|
852
|
-
debugLog("Invalid client handle");
|
|
853
|
-
}
|
|
854
|
-
const attributeId = this.itemToMonitor.attributeId;
|
|
855
|
-
// if dataFilter is specified ....
|
|
856
|
-
if (this.filter && this.filter instanceof node_opcua_service_subscription_2.DataChangeFilter) {
|
|
857
|
-
if (this.filter.trigger === node_opcua_service_subscription_2.DataChangeTrigger.Status) {
|
|
858
|
-
/** */
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
dataValue = (0, node_opcua_data_value_1.apply_timestamps)(dataValue, this.timestampsToReturn, attributeId);
|
|
862
|
-
return new node_opcua_service_subscription_1.MonitoredItemNotification({
|
|
863
|
-
clientHandle: this.clientHandle,
|
|
864
|
-
value: dataValue
|
|
865
|
-
});
|
|
866
|
-
}
|
|
867
|
-
/**
|
|
868
|
-
* @method _enqueue_value
|
|
869
|
-
* @param dataValue {DataValue} the dataValue to enqueue
|
|
870
|
-
* @private
|
|
871
|
-
*/
|
|
872
|
-
_enqueue_value(dataValue) {
|
|
873
|
-
// preconditions:
|
|
874
|
-
if (doDebug) {
|
|
875
|
-
debugLog("_enqueue_value = ", dataValue.toString());
|
|
876
|
-
}
|
|
877
|
-
(0, node_opcua_assert_1.assert)(dataValue instanceof node_opcua_data_value_1.DataValue);
|
|
878
|
-
// lets verify that, if status code is good then we have a valid Variant in the dataValue
|
|
879
|
-
(0, node_opcua_assert_1.assert)(!isGoodish(dataValue.statusCode) || dataValue.value instanceof node_opcua_variant_1.Variant);
|
|
880
|
-
// xx assert(isGoodish(dataValue.statusCode) || util.isNullOrUndefined(dataValue.value) );
|
|
881
|
-
// let's check that data Value is really a different object
|
|
882
|
-
// we may end up with corrupted queue if dataValue are recycled and stored as is in notifications
|
|
883
|
-
(0, node_opcua_assert_1.assert)(dataValue !== this.oldDataValue, "dataValue cannot be the same object twice!");
|
|
884
|
-
// Xx // todo ERN !!!! PLEASE CHECK this !!!
|
|
885
|
-
// Xx // let make a clone, so we have a snapshot
|
|
886
|
-
// Xx dataValue = dataValue.clone();
|
|
887
|
-
// let's check that data Value is really a different object
|
|
888
|
-
// we may end up with corrupted queue if dataValue are recycled and stored as is in notifications
|
|
889
|
-
(0, node_opcua_assert_1.assert)(!this.oldDataValue || !dataValue.value || dataValue.value !== this.oldDataValue.value, "dataValue cannot be the same object twice!");
|
|
890
|
-
if (!(!this.oldDataValue ||
|
|
891
|
-
!this.oldDataValue.value ||
|
|
892
|
-
!dataValue.value ||
|
|
893
|
-
!(dataValue.value.value instanceof Object) ||
|
|
894
|
-
dataValue.value.value !== this.oldDataValue.value.value) &&
|
|
895
|
-
!(dataValue.value.value instanceof Date)) {
|
|
896
|
-
throw new Error("dataValue.value.value cannot be the same object twice! " +
|
|
897
|
-
this.node.browseName.toString() +
|
|
898
|
-
" " +
|
|
899
|
-
dataValue.toString() +
|
|
900
|
-
" " +
|
|
901
|
-
chalk.cyan(this.oldDataValue.toString()));
|
|
902
|
-
}
|
|
903
|
-
// istanbul ignore next
|
|
904
|
-
if (doDebug) {
|
|
905
|
-
debugLog("MonitoredItem#_enqueue_value", this.node.nodeId.toString());
|
|
906
|
-
}
|
|
907
|
-
this.oldDataValue = dataValue;
|
|
908
|
-
const notification = this._makeDataChangeNotification(dataValue);
|
|
909
|
-
this._enqueue_notification(notification);
|
|
910
|
-
}
|
|
911
|
-
_makeEventFieldList(eventFields) {
|
|
912
|
-
(0, node_opcua_assert_1.assert)(Array.isArray(eventFields));
|
|
913
|
-
return new node_opcua_types_1.EventFieldList({
|
|
914
|
-
clientHandle: this.clientHandle,
|
|
915
|
-
eventFields
|
|
916
|
-
});
|
|
917
|
-
}
|
|
918
|
-
_enqueue_event(eventFields) {
|
|
919
|
-
if (doDebug) {
|
|
920
|
-
debugLog(" MonitoredItem#_enqueue_event");
|
|
921
|
-
}
|
|
922
|
-
const notification = this._makeEventFieldList(eventFields);
|
|
923
|
-
this._enqueue_notification(notification);
|
|
924
|
-
}
|
|
925
|
-
_empty_queue() {
|
|
926
|
-
// empty queue
|
|
927
|
-
this.queue = [];
|
|
928
|
-
this.overflow = false;
|
|
929
|
-
}
|
|
930
|
-
_clear_timer() {
|
|
931
|
-
if (this._samplingId) {
|
|
932
|
-
if (useCommonTimer) {
|
|
933
|
-
(0, node_sampler_1.removeFromTimer)(this);
|
|
934
|
-
}
|
|
935
|
-
else {
|
|
936
|
-
clearInterval(this._samplingId);
|
|
937
|
-
}
|
|
938
|
-
this._samplingId = undefined;
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
_set_timer() {
|
|
942
|
-
(0, node_opcua_assert_1.assert)(this.samplingInterval >= MonitoredItem.minimumSamplingInterval);
|
|
943
|
-
(0, node_opcua_assert_1.assert)(!this._samplingId);
|
|
944
|
-
if (useCommonTimer) {
|
|
945
|
-
this._samplingId = (0, node_sampler_1.appendToTimer)(this);
|
|
946
|
-
}
|
|
947
|
-
else {
|
|
948
|
-
// settle periodic sampling
|
|
949
|
-
this._samplingId = setInterval(() => {
|
|
950
|
-
this._on_sampling_timer();
|
|
951
|
-
}, this.samplingInterval);
|
|
952
|
-
}
|
|
953
|
-
// xx console.log("MonitoredItem#_set_timer",this._samplingId);
|
|
954
|
-
}
|
|
955
|
-
_adjust_queue_to_match_new_queue_size() {
|
|
956
|
-
// adjust queue size if necessary
|
|
957
|
-
if (this.queueSize < this.queue.length) {
|
|
958
|
-
if (this.discardOldest) {
|
|
959
|
-
this.queue.splice(0, this.queue.length - this.queueSize);
|
|
960
|
-
}
|
|
961
|
-
else {
|
|
962
|
-
const lastElement = this.queue[this.queue.length - 1];
|
|
963
|
-
// only keep queueSize first element, discard others
|
|
964
|
-
this.queue.splice(this.queueSize);
|
|
965
|
-
this.queue[this.queue.length - 1] = lastElement;
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
if (this.queueSize <= 1) {
|
|
969
|
-
this.overflow = false;
|
|
970
|
-
// unset OverFlowBit
|
|
971
|
-
if (this.queue.length === 1) {
|
|
972
|
-
if (this.queue[0] instanceof node_opcua_service_subscription_1.MonitoredItemNotification) {
|
|
973
|
-
const el = this.queue[0];
|
|
974
|
-
if (el.value.statusCode.hasOverflowBit) {
|
|
975
|
-
el.value.statusCode.unset("Overflow | InfoTypeDataValue");
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
(0, node_opcua_assert_1.assert)(this.queue.length <= this.queueSize);
|
|
981
|
-
}
|
|
982
|
-
_adjust_sampling(old_samplingInterval) {
|
|
983
|
-
if (old_samplingInterval !== this.samplingInterval) {
|
|
984
|
-
this._start_sampling(false);
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
_on_node_disposed(node) {
|
|
988
|
-
this._on_value_changed(new node_opcua_data_value_1.DataValue({
|
|
989
|
-
sourceTimestamp: new Date(),
|
|
990
|
-
statusCode: node_opcua_status_code_1.StatusCodes.BadNodeIdInvalid
|
|
991
|
-
}));
|
|
992
|
-
this._stop_sampling();
|
|
993
|
-
node.removeListener("dispose", this._on_node_disposed_listener);
|
|
994
|
-
this._on_node_disposed_listener = null;
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
exports.MonitoredItem = MonitoredItem;
|
|
998
|
-
MonitoredItem.registry = new node_opcua_object_registry_1.ObjectRegistry();
|
|
999
|
-
MonitoredItem.minimumSamplingInterval = 50; // 50 ms as a minimum sampling interval
|
|
1000
|
-
MonitoredItem.defaultSamplingInterval = 1500; // 1500 ms as a default sampling interval
|
|
1001
|
-
MonitoredItem.maximumSamplingInterval = 1000 * 60 * 60; // 1 hour !
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.MonitoredItem = void 0;
|
|
13
|
+
/**
|
|
14
|
+
* @module node-opcua-server
|
|
15
|
+
*/
|
|
16
|
+
const events_1 = require("events");
|
|
17
|
+
const chalk = require("chalk");
|
|
18
|
+
const node_opcua_assert_1 = require("node-opcua-assert");
|
|
19
|
+
const node_opcua_address_space_1 = require("node-opcua-address-space");
|
|
20
|
+
const node_opcua_service_filter_1 = require("node-opcua-service-filter");
|
|
21
|
+
const node_opcua_data_model_1 = require("node-opcua-data-model");
|
|
22
|
+
const node_opcua_data_model_2 = require("node-opcua-data-model");
|
|
23
|
+
const node_opcua_data_value_1 = require("node-opcua-data-value");
|
|
24
|
+
const node_opcua_debug_1 = require("node-opcua-debug");
|
|
25
|
+
const node_opcua_numeric_range_1 = require("node-opcua-numeric-range");
|
|
26
|
+
const node_opcua_object_registry_1 = require("node-opcua-object-registry");
|
|
27
|
+
const node_opcua_service_filter_2 = require("node-opcua-service-filter");
|
|
28
|
+
const node_opcua_service_read_1 = require("node-opcua-service-read");
|
|
29
|
+
const node_opcua_service_subscription_1 = require("node-opcua-service-subscription");
|
|
30
|
+
const node_opcua_service_subscription_2 = require("node-opcua-service-subscription");
|
|
31
|
+
const node_opcua_status_code_1 = require("node-opcua-status-code");
|
|
32
|
+
const node_opcua_types_1 = require("node-opcua-types");
|
|
33
|
+
const node_opcua_variant_1 = require("node-opcua-variant");
|
|
34
|
+
const node_sampler_1 = require("./node_sampler");
|
|
35
|
+
const validate_filter_1 = require("./validate_filter");
|
|
36
|
+
const check_where_clause_on_address_space_1 = require("./filter/check_where_clause_on_address_space");
|
|
37
|
+
const defaultItemToMonitor = new node_opcua_service_read_1.ReadValueId({
|
|
38
|
+
attributeId: node_opcua_data_model_2.AttributeIds.Value,
|
|
39
|
+
indexRange: undefined
|
|
40
|
+
});
|
|
41
|
+
const debugLog = (0, node_opcua_debug_1.make_debugLog)(__filename);
|
|
42
|
+
const doDebug = (0, node_opcua_debug_1.checkDebugFlag)(__filename);
|
|
43
|
+
const warningLog = (0, node_opcua_debug_1.make_warningLog)(__filename);
|
|
44
|
+
function _adjust_sampling_interval(samplingInterval, node_minimumSamplingInterval) {
|
|
45
|
+
(0, node_opcua_assert_1.assert)(typeof node_minimumSamplingInterval === "number", "expecting a number");
|
|
46
|
+
if (samplingInterval === 0) {
|
|
47
|
+
return node_minimumSamplingInterval === 0
|
|
48
|
+
? samplingInterval
|
|
49
|
+
: Math.max(MonitoredItem.minimumSamplingInterval, node_minimumSamplingInterval);
|
|
50
|
+
}
|
|
51
|
+
(0, node_opcua_assert_1.assert)(samplingInterval >= 0, " this case should have been prevented outside");
|
|
52
|
+
samplingInterval = samplingInterval || MonitoredItem.defaultSamplingInterval;
|
|
53
|
+
samplingInterval = Math.max(samplingInterval, MonitoredItem.minimumSamplingInterval);
|
|
54
|
+
samplingInterval = Math.min(samplingInterval, MonitoredItem.maximumSamplingInterval);
|
|
55
|
+
samplingInterval =
|
|
56
|
+
node_minimumSamplingInterval === 0 ? samplingInterval : Math.max(samplingInterval, node_minimumSamplingInterval);
|
|
57
|
+
return samplingInterval;
|
|
58
|
+
}
|
|
59
|
+
const maxQueueSize = 5000;
|
|
60
|
+
function _adjust_queue_size(queueSize) {
|
|
61
|
+
queueSize = Math.min(queueSize, maxQueueSize);
|
|
62
|
+
queueSize = Math.max(1, queueSize);
|
|
63
|
+
return queueSize;
|
|
64
|
+
}
|
|
65
|
+
function _validate_parameters(monitoringParameters) {
|
|
66
|
+
// xx assert(options instanceof MonitoringParameters);
|
|
67
|
+
(0, node_opcua_assert_1.assert)(Object.prototype.hasOwnProperty.call(monitoringParameters, "clientHandle"));
|
|
68
|
+
(0, node_opcua_assert_1.assert)(Object.prototype.hasOwnProperty.call(monitoringParameters, "samplingInterval"));
|
|
69
|
+
(0, node_opcua_assert_1.assert)(isFinite(monitoringParameters.clientHandle));
|
|
70
|
+
(0, node_opcua_assert_1.assert)(isFinite(monitoringParameters.samplingInterval));
|
|
71
|
+
(0, node_opcua_assert_1.assert)(typeof monitoringParameters.discardOldest === "boolean");
|
|
72
|
+
(0, node_opcua_assert_1.assert)(isFinite(monitoringParameters.queueSize));
|
|
73
|
+
(0, node_opcua_assert_1.assert)(monitoringParameters.queueSize >= 0);
|
|
74
|
+
}
|
|
75
|
+
function statusCodeHasChanged(newDataValue, oldDataValue) {
|
|
76
|
+
(0, node_opcua_assert_1.assert)(newDataValue instanceof node_opcua_data_value_1.DataValue);
|
|
77
|
+
(0, node_opcua_assert_1.assert)(oldDataValue instanceof node_opcua_data_value_1.DataValue);
|
|
78
|
+
return newDataValue.statusCode !== oldDataValue.statusCode;
|
|
79
|
+
}
|
|
80
|
+
function valueHasChanged(newDataValue, oldDataValue, deadbandType, deadbandValue) {
|
|
81
|
+
(0, node_opcua_assert_1.assert)(newDataValue instanceof node_opcua_data_value_1.DataValue);
|
|
82
|
+
(0, node_opcua_assert_1.assert)(oldDataValue instanceof node_opcua_data_value_1.DataValue);
|
|
83
|
+
switch (deadbandType) {
|
|
84
|
+
case node_opcua_service_subscription_2.DeadbandType.None:
|
|
85
|
+
(0, node_opcua_assert_1.assert)(newDataValue.value instanceof node_opcua_variant_1.Variant);
|
|
86
|
+
(0, node_opcua_assert_1.assert)(newDataValue.value instanceof node_opcua_variant_1.Variant);
|
|
87
|
+
// No Deadband calculation should be applied.
|
|
88
|
+
return (0, node_opcua_service_subscription_2.isOutsideDeadbandNone)(oldDataValue.value, newDataValue.value);
|
|
89
|
+
case node_opcua_service_subscription_2.DeadbandType.Absolute:
|
|
90
|
+
// AbsoluteDeadband
|
|
91
|
+
return (0, node_opcua_service_subscription_2.isOutsideDeadbandAbsolute)(oldDataValue.value, newDataValue.value, deadbandValue);
|
|
92
|
+
default: {
|
|
93
|
+
// Percent_2 PercentDeadband (This type is specified in Part 8).
|
|
94
|
+
(0, node_opcua_assert_1.assert)(deadbandType === node_opcua_service_subscription_2.DeadbandType.Percent);
|
|
95
|
+
// The range of the deadbandValue is from 0.0 to 100.0 Percent.
|
|
96
|
+
(0, node_opcua_assert_1.assert)(deadbandValue >= 0 && deadbandValue <= 100);
|
|
97
|
+
// DeadbandType = PercentDeadband
|
|
98
|
+
// For this type of deadband the deadbandValue is defined as the percentage of the EURange. That is,
|
|
99
|
+
// it applies only to AnalogItems with an EURange Property that defines the typical value range for the
|
|
100
|
+
// item. This range shall be multiplied with the deadbandValue and then compared to the actual value change
|
|
101
|
+
// to determine the need for a data change notification. The following pseudo code shows how the deadband
|
|
102
|
+
// is calculated:
|
|
103
|
+
// DataChange if (absolute value of (last cached value - current value) >
|
|
104
|
+
// (deadbandValue/100.0) * ((high-low) of EURange)))
|
|
105
|
+
//
|
|
106
|
+
// Specifying a deadbandValue outside of this range will be rejected and reported with the
|
|
107
|
+
// StatusCode BadDeadbandFilterInvalid (see Table 27).
|
|
108
|
+
// If the Value of the MonitoredItem is an array, then the deadband calculation logic shall be applied to
|
|
109
|
+
// each element of the array. If an element that requires a DataChange is found, then no further
|
|
110
|
+
// deadband checking is necessary and the entire array shall be returned.
|
|
111
|
+
(0, node_opcua_assert_1.assert)(this.node !== null, "expecting a valid address_space object here to get access the the EURange");
|
|
112
|
+
const euRangeNode = this.node.getChildByName("EURange");
|
|
113
|
+
if (euRangeNode && euRangeNode.nodeClass === node_opcua_data_model_1.NodeClass.Variable) {
|
|
114
|
+
// double,double
|
|
115
|
+
const rangeVariant = euRangeNode.readValue().value;
|
|
116
|
+
return (0, node_opcua_service_subscription_2.isOutsideDeadbandPercent)(oldDataValue.value, newDataValue.value, deadbandValue, rangeVariant.value);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
console.log("EURange is not of type Variable");
|
|
120
|
+
}
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function timestampHasChanged(t1, t2) {
|
|
126
|
+
if (t1 || !t2 || t2 || !t1) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
if (!t1 || !t2) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
return t1.getTime() !== t2.getTime();
|
|
133
|
+
}
|
|
134
|
+
function isGoodish(statusCode) {
|
|
135
|
+
return statusCode.value < 0x10000000;
|
|
136
|
+
}
|
|
137
|
+
function apply_dataChange_filter(newDataValue, oldDataValue) {
|
|
138
|
+
var _a;
|
|
139
|
+
/* istanbul ignore next */
|
|
140
|
+
if (!this.filter || !(this.filter instanceof node_opcua_service_subscription_2.DataChangeFilter)) {
|
|
141
|
+
throw new Error("Internal Error");
|
|
142
|
+
}
|
|
143
|
+
const trigger = this.filter.trigger;
|
|
144
|
+
// istanbul ignore next
|
|
145
|
+
if (doDebug) {
|
|
146
|
+
try {
|
|
147
|
+
debugLog("filter pass ?", node_opcua_service_subscription_2.DataChangeTrigger[trigger], (_a = this.oldDataValue) === null || _a === void 0 ? void 0 : _a.toString(), newDataValue.toString());
|
|
148
|
+
if (trigger === node_opcua_service_subscription_2.DataChangeTrigger.Status ||
|
|
149
|
+
trigger === node_opcua_service_subscription_2.DataChangeTrigger.StatusValue ||
|
|
150
|
+
trigger === node_opcua_service_subscription_2.DataChangeTrigger.StatusValueTimestamp) {
|
|
151
|
+
debugLog("statusCodeHasChanged ", statusCodeHasChanged(newDataValue, oldDataValue));
|
|
152
|
+
}
|
|
153
|
+
if (trigger === node_opcua_service_subscription_2.DataChangeTrigger.StatusValue || trigger === node_opcua_service_subscription_2.DataChangeTrigger.StatusValueTimestamp) {
|
|
154
|
+
debugLog("valueHasChanged ", valueHasChanged.call(this, newDataValue, oldDataValue, this.filter.deadbandType, this.filter.deadbandValue));
|
|
155
|
+
}
|
|
156
|
+
if (trigger === node_opcua_service_subscription_2.DataChangeTrigger.StatusValueTimestamp) {
|
|
157
|
+
debugLog("timestampHasChanged ", timestampHasChanged(newDataValue.sourceTimestamp, oldDataValue.sourceTimestamp));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
warningLog(err);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
switch (trigger) {
|
|
165
|
+
case node_opcua_service_subscription_2.DataChangeTrigger.Status: {
|
|
166
|
+
//
|
|
167
|
+
// Status
|
|
168
|
+
// Report a notification ONLY if the StatusCode associated with
|
|
169
|
+
// the value changes. See Table 166 for StatusCodes defined in
|
|
170
|
+
// this standard. Part 8 specifies additional StatusCodes that are
|
|
171
|
+
// valid in particular for device data.
|
|
172
|
+
return statusCodeHasChanged(newDataValue, oldDataValue);
|
|
173
|
+
}
|
|
174
|
+
case node_opcua_service_subscription_2.DataChangeTrigger.StatusValue: {
|
|
175
|
+
// filtering value changes.
|
|
176
|
+
// change. The Deadband filter can be used in addition for
|
|
177
|
+
// Report a notification if either the StatusCode or the value
|
|
178
|
+
// StatusValue
|
|
179
|
+
// This is the default setting if no filter is set.
|
|
180
|
+
return (statusCodeHasChanged(newDataValue, oldDataValue) ||
|
|
181
|
+
valueHasChanged.call(this, newDataValue, oldDataValue, this.filter.deadbandType, this.filter.deadbandValue));
|
|
182
|
+
}
|
|
183
|
+
default: {
|
|
184
|
+
// StatusValueTimestamp
|
|
185
|
+
// Report a notification if either StatusCode, value or the
|
|
186
|
+
// SourceTimestamp change.
|
|
187
|
+
//
|
|
188
|
+
// If a Deadband filter is specified,this trigger has the same behavior as STATUS_VALUE_1.
|
|
189
|
+
//
|
|
190
|
+
// If the DataChangeFilter is not applied to the monitored item, STATUS_VALUE_1
|
|
191
|
+
// is the default reporting behavior
|
|
192
|
+
(0, node_opcua_assert_1.assert)(trigger === node_opcua_service_subscription_2.DataChangeTrigger.StatusValueTimestamp);
|
|
193
|
+
return (timestampHasChanged(newDataValue.sourceTimestamp, oldDataValue.sourceTimestamp) ||
|
|
194
|
+
statusCodeHasChanged(newDataValue, oldDataValue) ||
|
|
195
|
+
valueHasChanged.call(this, newDataValue, oldDataValue, this.filter.deadbandType, this.filter.deadbandValue));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function apply_filter(newDataValue) {
|
|
200
|
+
if (!this.oldDataValue) {
|
|
201
|
+
return true; // keep
|
|
202
|
+
}
|
|
203
|
+
if (this.filter instanceof node_opcua_service_subscription_2.DataChangeFilter) {
|
|
204
|
+
return apply_dataChange_filter.call(this, newDataValue, this.oldDataValue);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
// if filter not set, by default report changes to Status or Value only
|
|
208
|
+
if (newDataValue.statusCode.value !== this.oldDataValue.statusCode.value) {
|
|
209
|
+
return true; // Keep because statusCode has changed ...
|
|
210
|
+
}
|
|
211
|
+
return !(0, node_opcua_variant_1.sameVariant)(newDataValue.value, this.oldDataValue.value);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function setSemanticChangeBit(notification) {
|
|
215
|
+
if (notification instanceof node_opcua_service_subscription_1.MonitoredItemNotification) {
|
|
216
|
+
notification.value.statusCode = node_opcua_status_code_1.StatusCode.makeStatusCode(notification.value.statusCode || node_opcua_status_code_1.StatusCodes.Good, "SemanticChanged");
|
|
217
|
+
}
|
|
218
|
+
else if (notification instanceof node_opcua_data_value_1.DataValue) {
|
|
219
|
+
notification.statusCode = node_opcua_status_code_1.StatusCode.makeStatusCode(notification.statusCode || node_opcua_status_code_1.StatusCodes.Good, "SemanticChanged");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const useCommonTimer = true;
|
|
223
|
+
function isSourceNewerThan(a, b) {
|
|
224
|
+
var _a, _b;
|
|
225
|
+
if (!b) {
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
const at = ((_a = a.sourceTimestamp) === null || _a === void 0 ? void 0 : _a.getTime()) || 0;
|
|
229
|
+
const bt = ((_b = b.sourceTimestamp) === null || _b === void 0 ? void 0 : _b.getTime()) || 0;
|
|
230
|
+
if (at === bt) {
|
|
231
|
+
return a.sourcePicoseconds > b.sourcePicoseconds;
|
|
232
|
+
}
|
|
233
|
+
return at > bt;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* a server side monitored item
|
|
237
|
+
*
|
|
238
|
+
* - Once created, the MonitoredItem will raised an "samplingEvent" event every "samplingInterval" millisecond
|
|
239
|
+
* until {{#crossLink "MonitoredItem/terminate:method"}}{{/crossLink}} is called.
|
|
240
|
+
*
|
|
241
|
+
* - It is up to the event receiver to call {{#crossLink "MonitoredItem/recordValue:method"}}{{/crossLink}}.
|
|
242
|
+
*
|
|
243
|
+
*/
|
|
244
|
+
class MonitoredItem extends events_1.EventEmitter {
|
|
245
|
+
constructor(options) {
|
|
246
|
+
super();
|
|
247
|
+
this.samplingInterval = -1;
|
|
248
|
+
this.discardOldest = true;
|
|
249
|
+
this.queueSize = 0;
|
|
250
|
+
this.samplingFunc = null;
|
|
251
|
+
this._is_sampling = false;
|
|
252
|
+
(0, node_opcua_assert_1.assert)(Object.prototype.hasOwnProperty.call(options, "monitoredItemId"));
|
|
253
|
+
(0, node_opcua_assert_1.assert)(!options.monitoringMode, "use setMonitoring mode explicitly to activate the monitored item");
|
|
254
|
+
options.itemToMonitor = options.itemToMonitor || defaultItemToMonitor;
|
|
255
|
+
this._samplingId = undefined;
|
|
256
|
+
this.clientHandle = 0; // invalid
|
|
257
|
+
this.filter = null;
|
|
258
|
+
this._set_parameters(options);
|
|
259
|
+
this.monitoredItemId = options.monitoredItemId; // ( known as serverHandle)
|
|
260
|
+
this.queue = [];
|
|
261
|
+
this.overflow = false;
|
|
262
|
+
this.oldDataValue = new node_opcua_data_value_1.DataValue({ statusCode: node_opcua_status_code_1.StatusCodes.BadDataUnavailable }); // unset initially
|
|
263
|
+
// user has to call setMonitoringMode
|
|
264
|
+
this.monitoringMode = node_opcua_service_subscription_1.MonitoringMode.Invalid;
|
|
265
|
+
this.timestampsToReturn = (0, node_opcua_data_value_1.coerceTimestampsToReturn)(options.timestampsToReturn);
|
|
266
|
+
this.itemToMonitor = options.itemToMonitor;
|
|
267
|
+
this._node = null;
|
|
268
|
+
this._semantic_version = 0;
|
|
269
|
+
if (doDebug) {
|
|
270
|
+
debugLog("Monitoring ", options.itemToMonitor.toString());
|
|
271
|
+
}
|
|
272
|
+
this._on_node_disposed_listener = null;
|
|
273
|
+
MonitoredItem.registry.register(this);
|
|
274
|
+
}
|
|
275
|
+
get node() {
|
|
276
|
+
return this._node;
|
|
277
|
+
}
|
|
278
|
+
set node(someNode) {
|
|
279
|
+
throw new Error("Unexpected way to set node");
|
|
280
|
+
}
|
|
281
|
+
setNode(node) {
|
|
282
|
+
(0, node_opcua_assert_1.assert)(!this.node || this.node === node, "node already set");
|
|
283
|
+
this._node = node;
|
|
284
|
+
this._semantic_version = node.semantic_version;
|
|
285
|
+
this._on_node_disposed_listener = () => this._on_node_disposed(this._node);
|
|
286
|
+
this._node.on("dispose", this._on_node_disposed_listener);
|
|
287
|
+
}
|
|
288
|
+
setMonitoringMode(monitoringMode) {
|
|
289
|
+
(0, node_opcua_assert_1.assert)(monitoringMode !== node_opcua_service_subscription_1.MonitoringMode.Invalid);
|
|
290
|
+
if (monitoringMode === this.monitoringMode) {
|
|
291
|
+
// nothing to do
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const old_monitoringMode = this.monitoringMode;
|
|
295
|
+
this.monitoringMode = monitoringMode;
|
|
296
|
+
if (this.monitoringMode === node_opcua_service_subscription_1.MonitoringMode.Disabled) {
|
|
297
|
+
this._stop_sampling();
|
|
298
|
+
// OPCUA 1.03 part 4 : $5.12.4
|
|
299
|
+
// setting the mode to DISABLED causes all queued Notifications to be deleted
|
|
300
|
+
this._empty_queue();
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
(0, node_opcua_assert_1.assert)(this.monitoringMode === node_opcua_service_subscription_1.MonitoringMode.Sampling || this.monitoringMode === node_opcua_service_subscription_1.MonitoringMode.Reporting);
|
|
304
|
+
// OPCUA 1.03 part 4 : $5.12.1.3
|
|
305
|
+
// When a MonitoredItem is enabled (i.e. when the MonitoringMode is changed from DISABLED to
|
|
306
|
+
// SAMPLING or REPORTING) or it is created in the enabled state, the Server shall report the first
|
|
307
|
+
// sample as soon as possible and the time of this sample becomes the starting point for the next
|
|
308
|
+
// sampling interval.
|
|
309
|
+
const recordInitialValue = old_monitoringMode === node_opcua_service_subscription_1.MonitoringMode.Invalid || old_monitoringMode === node_opcua_service_subscription_1.MonitoringMode.Disabled;
|
|
310
|
+
this._start_sampling(recordInitialValue);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Terminate the MonitoredItem.
|
|
315
|
+
* @method terminate
|
|
316
|
+
*
|
|
317
|
+
* This will stop the internal sampling timer.
|
|
318
|
+
*/
|
|
319
|
+
terminate() {
|
|
320
|
+
this._stop_sampling();
|
|
321
|
+
}
|
|
322
|
+
dispose() {
|
|
323
|
+
if (doDebug) {
|
|
324
|
+
debugLog("DISPOSING MONITORED ITEM", this._node.nodeId.toString());
|
|
325
|
+
}
|
|
326
|
+
this._stop_sampling();
|
|
327
|
+
MonitoredItem.registry.unregister(this);
|
|
328
|
+
if (this._on_node_disposed_listener) {
|
|
329
|
+
this._node.removeListener("dispose", this._on_node_disposed_listener);
|
|
330
|
+
this._on_node_disposed_listener = null;
|
|
331
|
+
}
|
|
332
|
+
// x assert(this._samplingId === null,"Sampling Id must be null");
|
|
333
|
+
this.oldDataValue = undefined;
|
|
334
|
+
this.queue = [];
|
|
335
|
+
this.itemToMonitor = null;
|
|
336
|
+
this.filter = null;
|
|
337
|
+
this.monitoredItemId = 0;
|
|
338
|
+
this._node = null;
|
|
339
|
+
this._semantic_version = 0;
|
|
340
|
+
this.$subscription = undefined;
|
|
341
|
+
this.removeAllListeners();
|
|
342
|
+
(0, node_opcua_assert_1.assert)(!this._samplingId);
|
|
343
|
+
(0, node_opcua_assert_1.assert)(!this._value_changed_callback);
|
|
344
|
+
(0, node_opcua_assert_1.assert)(!this._semantic_changed_callback);
|
|
345
|
+
(0, node_opcua_assert_1.assert)(!this._attribute_changed_callback);
|
|
346
|
+
(0, node_opcua_assert_1.assert)(!this._on_opcua_event_received_callback);
|
|
347
|
+
this._on_opcua_event_received_callback = null;
|
|
348
|
+
this._attribute_changed_callback = null;
|
|
349
|
+
this._semantic_changed_callback = null;
|
|
350
|
+
this._on_opcua_event_received_callback = null;
|
|
351
|
+
}
|
|
352
|
+
get isSampling() {
|
|
353
|
+
return (!!this._samplingId ||
|
|
354
|
+
typeof this._value_changed_callback === "function" ||
|
|
355
|
+
typeof this._attribute_changed_callback === "function");
|
|
356
|
+
}
|
|
357
|
+
toString() {
|
|
358
|
+
var _a;
|
|
359
|
+
let str = "";
|
|
360
|
+
str += `monitored item nodeId : ${(_a = this.node) === null || _a === void 0 ? void 0 : _a.nodeId.toString()} \n`;
|
|
361
|
+
str += ` sampling interval : ${this.samplingInterval} \n`;
|
|
362
|
+
str += ` monitoredItemId : ${this.monitoredItemId} \n`;
|
|
363
|
+
return str;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* @param dataValue the whole dataValue
|
|
367
|
+
* @param skipChangeTest indicates whether recordValue should not check that dataValue is really
|
|
368
|
+
* different from previous one, ( by checking timestamps but also variant value)
|
|
369
|
+
* @private
|
|
370
|
+
*
|
|
371
|
+
* Notes:
|
|
372
|
+
* - recordValue can only be called within timer event
|
|
373
|
+
* - for performance reason, dataValue may be a shared value with the underlying node,
|
|
374
|
+
* therefore recordValue must clone the dataValue to make sure it retains a snapshot
|
|
375
|
+
* of the contain at the time recordValue was called.
|
|
376
|
+
*
|
|
377
|
+
*/
|
|
378
|
+
recordValue(dataValue, skipChangeTest, indexRange) {
|
|
379
|
+
var _a;
|
|
380
|
+
(0, node_opcua_assert_1.assert)(dataValue instanceof node_opcua_data_value_1.DataValue);
|
|
381
|
+
(0, node_opcua_assert_1.assert)(dataValue !== this.oldDataValue, "recordValue expects different dataValue to be provided");
|
|
382
|
+
(0, node_opcua_assert_1.assert)(!dataValue.value || dataValue.value !== this.oldDataValue.value, "recordValue expects different dataValue.value to be provided");
|
|
383
|
+
(0, node_opcua_assert_1.assert)(!dataValue.value || dataValue.value.isValid(), "expecting a valid variant value");
|
|
384
|
+
const hasSemanticChanged = this.node && this.node.semantic_version !== this._semantic_version;
|
|
385
|
+
// xx console.log("`\n----------------------------",skipChangeTest,this.clientHandle,
|
|
386
|
+
// this.node.listenerCount("value_changed"),this.node.nodeId.toString());
|
|
387
|
+
// xx console.log("events ---- ",this.node.eventNames().join("-"));
|
|
388
|
+
// xx console.log("indexRange = ",indexRange ? indexRange.toString() :"");
|
|
389
|
+
// xx console.log("this.itemToMonitor.indexRange = ",this.itemToMonitor.indexRange.toString());
|
|
390
|
+
if (!hasSemanticChanged && indexRange && this.itemToMonitor.indexRange) {
|
|
391
|
+
// we just ignore changes that do not fall within our range
|
|
392
|
+
// ( unless semantic bit has changed )
|
|
393
|
+
if (!node_opcua_numeric_range_1.NumericRange.overlap(indexRange, this.itemToMonitor.indexRange)) {
|
|
394
|
+
return; // no overlap !
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
(0, node_opcua_assert_1.assert)(this.itemToMonitor, "must have a valid itemToMonitor(have this monitoredItem been disposed already ?");
|
|
398
|
+
// extract the range that we are interested with
|
|
399
|
+
dataValue = (0, node_opcua_data_value_1.extractRange)(dataValue, this.itemToMonitor.indexRange);
|
|
400
|
+
// istanbul ignore next
|
|
401
|
+
if (doDebug) {
|
|
402
|
+
debugLog("MonitoredItem#recordValue", this.node.nodeId.toString(), this.node.browseName.toString(), " has Changed = ", !(0, node_opcua_data_value_1.sameDataValue)(dataValue, this.oldDataValue), "skipChangeTest = ", skipChangeTest, "hasSemanticChanged = ", hasSemanticChanged);
|
|
403
|
+
}
|
|
404
|
+
// if semantic has changed, value need to be enqueued regardless of other assumptions
|
|
405
|
+
if (hasSemanticChanged) {
|
|
406
|
+
debugLog("_enqueue_value => because hasSemanticChanged");
|
|
407
|
+
setSemanticChangeBit(dataValue);
|
|
408
|
+
this._semantic_version = this.node.semantic_version;
|
|
409
|
+
return this._enqueue_value(dataValue);
|
|
410
|
+
}
|
|
411
|
+
const useIndexRange = this.itemToMonitor.indexRange && !this.itemToMonitor.indexRange.isEmpty();
|
|
412
|
+
if (!skipChangeTest) {
|
|
413
|
+
const hasChanged = !(0, node_opcua_data_value_1.sameDataValue)(dataValue, this.oldDataValue);
|
|
414
|
+
if (!hasChanged) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (!apply_filter.call(this, dataValue)) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (useIndexRange) {
|
|
422
|
+
// when an indexRange is provided , make sure that no record happens unless
|
|
423
|
+
// extracted variant in the selected range has really changed.
|
|
424
|
+
// istanbul ignore next
|
|
425
|
+
if (doDebug) {
|
|
426
|
+
debugLog("Current : ", this.oldDataValue.toString());
|
|
427
|
+
debugLog("New : ", dataValue.toString());
|
|
428
|
+
debugLog("indexRange=", indexRange);
|
|
429
|
+
}
|
|
430
|
+
if ((0, node_opcua_variant_1.sameVariant)(dataValue.value, this.oldDataValue.value)) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
// processTriggerItems
|
|
435
|
+
this.triggerLinkedItems();
|
|
436
|
+
if (doDebug) {
|
|
437
|
+
debugLog("RECORD VALUE ", (_a = this.node) === null || _a === void 0 ? void 0 : _a.nodeId.toString());
|
|
438
|
+
}
|
|
439
|
+
// store last value
|
|
440
|
+
this._enqueue_value(dataValue);
|
|
441
|
+
}
|
|
442
|
+
hasLinkItem(linkedMonitoredItemId) {
|
|
443
|
+
if (!this._linkedItems) {
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
return this._linkedItems.findIndex((x) => x === linkedMonitoredItemId) > 0;
|
|
447
|
+
}
|
|
448
|
+
addLinkItem(linkedMonitoredItemId) {
|
|
449
|
+
if (linkedMonitoredItemId === this.monitoredItemId) {
|
|
450
|
+
return node_opcua_status_code_1.StatusCodes.BadMonitoredItemIdInvalid;
|
|
451
|
+
}
|
|
452
|
+
this._linkedItems = this._linkedItems || [];
|
|
453
|
+
if (this.hasLinkItem(linkedMonitoredItemId)) {
|
|
454
|
+
return node_opcua_status_code_1.StatusCodes.BadMonitoredItemIdInvalid; // nothing to do
|
|
455
|
+
}
|
|
456
|
+
this._linkedItems.push(linkedMonitoredItemId);
|
|
457
|
+
return node_opcua_status_code_1.StatusCodes.Good;
|
|
458
|
+
}
|
|
459
|
+
removeLinkItem(linkedMonitoredItemId) {
|
|
460
|
+
if (!this._linkedItems || linkedMonitoredItemId === this.monitoredItemId) {
|
|
461
|
+
return node_opcua_status_code_1.StatusCodes.BadMonitoredItemIdInvalid;
|
|
462
|
+
}
|
|
463
|
+
const index = this._linkedItems.findIndex((x) => x === linkedMonitoredItemId);
|
|
464
|
+
if (index === -1) {
|
|
465
|
+
return node_opcua_status_code_1.StatusCodes.BadMonitoredItemIdInvalid;
|
|
466
|
+
}
|
|
467
|
+
this._linkedItems.splice(index, 1);
|
|
468
|
+
return node_opcua_status_code_1.StatusCodes.Good;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* @internals
|
|
472
|
+
*/
|
|
473
|
+
triggerLinkedItems() {
|
|
474
|
+
var _a, _b;
|
|
475
|
+
if (!this.$subscription || !this._linkedItems) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
// see https://reference.opcfoundation.org/v104/Core/docs/Part4/5.12.1/#5.12.1.6
|
|
479
|
+
for (const linkItem of this._linkedItems) {
|
|
480
|
+
const linkedMonitoredItem = this.$subscription.getMonitoredItem(linkItem);
|
|
481
|
+
if (!linkedMonitoredItem) {
|
|
482
|
+
// monitoredItem may have been deleted
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
if (linkedMonitoredItem.monitoringMode === node_opcua_service_subscription_1.MonitoringMode.Disabled) {
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
if (linkedMonitoredItem.monitoringMode === node_opcua_service_subscription_1.MonitoringMode.Reporting) {
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
(0, node_opcua_assert_1.assert)(linkedMonitoredItem.monitoringMode === node_opcua_service_subscription_1.MonitoringMode.Sampling);
|
|
492
|
+
// istanbul ignore next
|
|
493
|
+
if (doDebug) {
|
|
494
|
+
debugLog("triggerLinkedItems => ", (_a = this.node) === null || _a === void 0 ? void 0 : _a.nodeId.toString(), (_b = linkedMonitoredItem.node) === null || _b === void 0 ? void 0 : _b.nodeId.toString());
|
|
495
|
+
}
|
|
496
|
+
linkedMonitoredItem.trigger();
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
get hasMonitoredItemNotifications() {
|
|
500
|
+
return this.queue.length > 0 || (this._triggeredNotifications !== undefined && this._triggeredNotifications.length > 0);
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* @internals
|
|
504
|
+
*/
|
|
505
|
+
trigger() {
|
|
506
|
+
setImmediate(() => {
|
|
507
|
+
this._triggeredNotifications = this._triggeredNotifications || [];
|
|
508
|
+
const notifications = this.extractMonitoredItemNotifications(true);
|
|
509
|
+
this._triggeredNotifications = [].concat(this._triggeredNotifications, notifications);
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
extractMonitoredItemNotifications(bForce = false) {
|
|
513
|
+
if (!bForce && this.monitoringMode === node_opcua_service_subscription_1.MonitoringMode.Sampling && this._triggeredNotifications) {
|
|
514
|
+
const notifications1 = this._triggeredNotifications;
|
|
515
|
+
this._triggeredNotifications = undefined;
|
|
516
|
+
return notifications1;
|
|
517
|
+
}
|
|
518
|
+
if (!bForce && this.monitoringMode !== node_opcua_service_subscription_1.MonitoringMode.Reporting) {
|
|
519
|
+
return [];
|
|
520
|
+
}
|
|
521
|
+
const notifications = this.queue;
|
|
522
|
+
this._empty_queue();
|
|
523
|
+
// apply semantic changed bit if necessary
|
|
524
|
+
if (notifications.length > 0 && this.node && this._semantic_version < this.node.semantic_version) {
|
|
525
|
+
const dataValue = notifications[notifications.length - 1];
|
|
526
|
+
setSemanticChangeBit(dataValue);
|
|
527
|
+
(0, node_opcua_assert_1.assert)(this.node.nodeClass === node_opcua_data_model_1.NodeClass.Variable);
|
|
528
|
+
this._semantic_version = this.node.semantic_version;
|
|
529
|
+
}
|
|
530
|
+
return notifications;
|
|
531
|
+
}
|
|
532
|
+
modify(timestampsToReturn, monitoringParameters) {
|
|
533
|
+
(0, node_opcua_assert_1.assert)(monitoringParameters instanceof node_opcua_service_subscription_1.MonitoringParameters);
|
|
534
|
+
const old_samplingInterval = this.samplingInterval;
|
|
535
|
+
this.timestampsToReturn = timestampsToReturn || this.timestampsToReturn;
|
|
536
|
+
if (old_samplingInterval !== 0 && monitoringParameters.samplingInterval === 0) {
|
|
537
|
+
monitoringParameters.samplingInterval = MonitoredItem.minimumSamplingInterval; // fastest possible
|
|
538
|
+
}
|
|
539
|
+
// spec says: Illegal request values for parameters that can be revised do not generate errors. Instead the
|
|
540
|
+
// server will choose default values and indicate them in the corresponding revised parameter
|
|
541
|
+
this._set_parameters(monitoringParameters);
|
|
542
|
+
this._adjust_queue_to_match_new_queue_size();
|
|
543
|
+
this._adjust_sampling(old_samplingInterval);
|
|
544
|
+
if (monitoringParameters.filter) {
|
|
545
|
+
const statusCodeFilter = (0, validate_filter_1.validateFilter)(monitoringParameters.filter, this.itemToMonitor, this.node);
|
|
546
|
+
if (statusCodeFilter.isNot(node_opcua_status_code_1.StatusCodes.Good)) {
|
|
547
|
+
return new node_opcua_service_subscription_1.MonitoredItemModifyResult({
|
|
548
|
+
statusCode: statusCodeFilter
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
// validate filter
|
|
553
|
+
// note : The DataChangeFilter does not have an associated result structure.
|
|
554
|
+
const filterResult = null; // new subscription_service.DataChangeFilter
|
|
555
|
+
return new node_opcua_service_subscription_1.MonitoredItemModifyResult({
|
|
556
|
+
filterResult,
|
|
557
|
+
revisedQueueSize: this.queueSize,
|
|
558
|
+
revisedSamplingInterval: this.samplingInterval,
|
|
559
|
+
statusCode: node_opcua_status_code_1.StatusCodes.Good
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
resendInitialValues() {
|
|
563
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
564
|
+
// tte first Publish response(s) after the TransferSubscriptions call shall contain the current values of all
|
|
565
|
+
// Monitored Items in the Subscription where the Monitoring Mode is set to Reporting.
|
|
566
|
+
// the first Publish response after the TransferSubscriptions call shall contain only the value changes since
|
|
567
|
+
// the last Publish response was sent.
|
|
568
|
+
// This parameter only applies to MonitoredItems used for monitoring Attribute changes.
|
|
569
|
+
this._stop_sampling();
|
|
570
|
+
return this._start_sampling(true);
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* @method _on_sampling_timer
|
|
575
|
+
* @private
|
|
576
|
+
* request
|
|
577
|
+
*
|
|
578
|
+
*/
|
|
579
|
+
_on_sampling_timer() {
|
|
580
|
+
// istanbul ignore next
|
|
581
|
+
if (doDebug) {
|
|
582
|
+
debugLog("MonitoredItem#_on_sampling_timer", this.node ? this.node.nodeId.toString() : "null", " isSampling?=", this._is_sampling);
|
|
583
|
+
}
|
|
584
|
+
if (this._samplingId) {
|
|
585
|
+
(0, node_opcua_assert_1.assert)(this.monitoringMode === node_opcua_service_subscription_1.MonitoringMode.Sampling || this.monitoringMode === node_opcua_service_subscription_1.MonitoringMode.Reporting);
|
|
586
|
+
if (this._is_sampling) {
|
|
587
|
+
// previous sampling call is not yet completed..
|
|
588
|
+
// there is nothing we can do about it except waiting until next tick.
|
|
589
|
+
// note : see also issue #156 on github
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
// xx console.log("xxxx ON SAMPLING");
|
|
593
|
+
(0, node_opcua_assert_1.assert)(!this._is_sampling, "sampling func shall not be re-entrant !! fix it");
|
|
594
|
+
if (!this.samplingFunc) {
|
|
595
|
+
throw new Error("internal error : missing samplingFunc");
|
|
596
|
+
}
|
|
597
|
+
this._is_sampling = true;
|
|
598
|
+
this.samplingFunc.call(this, this.oldDataValue, (err, newDataValue) => {
|
|
599
|
+
if (!this._samplingId) {
|
|
600
|
+
// item has been disposed. The monitored item has been disposed while the async sampling func
|
|
601
|
+
// was taking place ... just ignore this
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (err) {
|
|
605
|
+
console.log(" SAMPLING ERROR =>", err);
|
|
606
|
+
}
|
|
607
|
+
else {
|
|
608
|
+
// only record value if source timestamp is newer
|
|
609
|
+
// xx if (newDataValue && isSourceNewerThan(newDataValue, this.oldDataValue)) {
|
|
610
|
+
this._on_value_changed(newDataValue);
|
|
611
|
+
// xx }
|
|
612
|
+
}
|
|
613
|
+
this._is_sampling = false;
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
/* istanbul ignore next */
|
|
618
|
+
debugLog("_on_sampling_timer call but MonitoredItem has been terminated !!! ");
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
_stop_sampling() {
|
|
622
|
+
// debugLog("MonitoredItem#_stop_sampling");
|
|
623
|
+
/* istanbul ignore next */
|
|
624
|
+
if (!this.node) {
|
|
625
|
+
throw new Error("Internal Error");
|
|
626
|
+
}
|
|
627
|
+
if (this._on_opcua_event_received_callback) {
|
|
628
|
+
(0, node_opcua_assert_1.assert)(typeof this._on_opcua_event_received_callback === "function");
|
|
629
|
+
this.node.removeListener("event", this._on_opcua_event_received_callback);
|
|
630
|
+
this._on_opcua_event_received_callback = null;
|
|
631
|
+
}
|
|
632
|
+
if (this._attribute_changed_callback) {
|
|
633
|
+
(0, node_opcua_assert_1.assert)(typeof this._attribute_changed_callback === "function");
|
|
634
|
+
const event_name = (0, node_opcua_address_space_1.makeAttributeEventName)(this.itemToMonitor.attributeId);
|
|
635
|
+
this.node.removeListener(event_name, this._attribute_changed_callback);
|
|
636
|
+
this._attribute_changed_callback = null;
|
|
637
|
+
}
|
|
638
|
+
if (this._value_changed_callback) {
|
|
639
|
+
// samplingInterval was 0 for a exception-based data Item
|
|
640
|
+
// we setup a event listener that we need to unwind here
|
|
641
|
+
(0, node_opcua_assert_1.assert)(typeof this._value_changed_callback === "function");
|
|
642
|
+
(0, node_opcua_assert_1.assert)(!this._samplingId);
|
|
643
|
+
this.node.removeListener("value_changed", this._value_changed_callback);
|
|
644
|
+
this._value_changed_callback = null;
|
|
645
|
+
}
|
|
646
|
+
if (this._semantic_changed_callback) {
|
|
647
|
+
(0, node_opcua_assert_1.assert)(typeof this._semantic_changed_callback === "function");
|
|
648
|
+
(0, node_opcua_assert_1.assert)(!this._samplingId);
|
|
649
|
+
this.node.removeListener("semantic_changed", this._semantic_changed_callback);
|
|
650
|
+
this._semantic_changed_callback = null;
|
|
651
|
+
}
|
|
652
|
+
if (this._samplingId) {
|
|
653
|
+
this._clear_timer();
|
|
654
|
+
}
|
|
655
|
+
(0, node_opcua_assert_1.assert)(!this._samplingId);
|
|
656
|
+
(0, node_opcua_assert_1.assert)(!this._value_changed_callback);
|
|
657
|
+
(0, node_opcua_assert_1.assert)(!this._semantic_changed_callback);
|
|
658
|
+
(0, node_opcua_assert_1.assert)(!this._attribute_changed_callback);
|
|
659
|
+
(0, node_opcua_assert_1.assert)(!this._on_opcua_event_received_callback);
|
|
660
|
+
}
|
|
661
|
+
_on_value_changed(dataValue, indexRange) {
|
|
662
|
+
(0, node_opcua_assert_1.assert)(dataValue instanceof node_opcua_data_value_1.DataValue);
|
|
663
|
+
this.recordValue(dataValue, false, indexRange);
|
|
664
|
+
}
|
|
665
|
+
_on_semantic_changed() {
|
|
666
|
+
const dataValue = this.node.readValue();
|
|
667
|
+
this._on_value_changed(dataValue);
|
|
668
|
+
}
|
|
669
|
+
_on_opcua_event(eventData) {
|
|
670
|
+
// TO DO : => Improve Filtering, bearing in mind that ....
|
|
671
|
+
// Release 1.04 8 OPC Unified Architecture, Part 9
|
|
672
|
+
// 4.5 Condition state synchronization
|
|
673
|
+
// To ensure a Client is always informed, the three special EventTypes
|
|
674
|
+
// (RefreshEndEventType, RefreshStartEventType and RefreshRequiredEventType)
|
|
675
|
+
// ignore the Event content filtering associated with a Subscription and will always be
|
|
676
|
+
// delivered to the Client.
|
|
677
|
+
var _a;
|
|
678
|
+
// istanbul ignore next
|
|
679
|
+
if (!this.filter || !(this.filter instanceof node_opcua_service_filter_2.EventFilter)) {
|
|
680
|
+
throw new Error("Internal Error : a EventFilter is requested");
|
|
681
|
+
}
|
|
682
|
+
const addressSpace = (_a = eventData.$eventDataSource) === null || _a === void 0 ? void 0 : _a.addressSpace;
|
|
683
|
+
if (!(0, check_where_clause_on_address_space_1.checkWhereClauseOnAdressSpace)(addressSpace, node_opcua_address_space_1.SessionContext.defaultContext, this.filter.whereClause, eventData)) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
const selectClauses = this.filter.selectClauses ? this.filter.selectClauses : [];
|
|
687
|
+
const eventFields = (0, node_opcua_service_filter_1.extractEventFields)(node_opcua_address_space_1.SessionContext.defaultContext, selectClauses, eventData);
|
|
688
|
+
// istanbul ignore next
|
|
689
|
+
if (doDebug) {
|
|
690
|
+
console.log(" RECEIVED INTERNAL EVENT THAT WE ARE MONITORING");
|
|
691
|
+
console.log(this.filter ? this.filter.toString() : "no filter");
|
|
692
|
+
eventFields.forEach((e) => {
|
|
693
|
+
console.log(e.toString());
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
this._enqueue_event(eventFields);
|
|
697
|
+
}
|
|
698
|
+
_getSession() {
|
|
699
|
+
if (!this.$subscription) {
|
|
700
|
+
return null;
|
|
701
|
+
}
|
|
702
|
+
if (!this.$subscription.$session) {
|
|
703
|
+
return null;
|
|
704
|
+
}
|
|
705
|
+
return this.$subscription.$session;
|
|
706
|
+
}
|
|
707
|
+
_start_sampling(recordInitialValue) {
|
|
708
|
+
// istanbul ignore next
|
|
709
|
+
if (!this.node) {
|
|
710
|
+
throw new Error("Internal Error");
|
|
711
|
+
}
|
|
712
|
+
setImmediate(() => this.__start_sampling(recordInitialValue));
|
|
713
|
+
}
|
|
714
|
+
__start_sampling(recordInitialValue) {
|
|
715
|
+
// istanbul ignore next
|
|
716
|
+
if (!this.node) {
|
|
717
|
+
return; // we just want to ignore here ...
|
|
718
|
+
}
|
|
719
|
+
// make sure oldDataValue is scrapped so first data recording can happen
|
|
720
|
+
this.oldDataValue = new node_opcua_data_value_1.DataValue({ statusCode: node_opcua_status_code_1.StatusCodes.BadDataUnavailable }); // unset initially
|
|
721
|
+
this._stop_sampling();
|
|
722
|
+
const context = new node_opcua_address_space_1.SessionContext({
|
|
723
|
+
session: this._getSession()
|
|
724
|
+
});
|
|
725
|
+
if (this.itemToMonitor.attributeId === node_opcua_data_model_2.AttributeIds.EventNotifier) {
|
|
726
|
+
// istanbul ignore next
|
|
727
|
+
if (doDebug) {
|
|
728
|
+
debugLog("xxxxxx monitoring EventNotifier on", this.node.nodeId.toString(), this.node.browseName.toString());
|
|
729
|
+
}
|
|
730
|
+
// we are monitoring OPCUA Event
|
|
731
|
+
this._on_opcua_event_received_callback = this._on_opcua_event.bind(this);
|
|
732
|
+
this.node.on("event", this._on_opcua_event_received_callback);
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
if (this.itemToMonitor.attributeId !== node_opcua_data_model_2.AttributeIds.Value) {
|
|
736
|
+
// sampling interval only applies to Value Attributes.
|
|
737
|
+
this.samplingInterval = 0; // turned to exception-based regardless of requested sampling interval
|
|
738
|
+
// non value attribute only react on value change
|
|
739
|
+
this._attribute_changed_callback = this._on_value_changed.bind(this);
|
|
740
|
+
const event_name = (0, node_opcua_address_space_1.makeAttributeEventName)(this.itemToMonitor.attributeId);
|
|
741
|
+
this.node.on(event_name, this._attribute_changed_callback);
|
|
742
|
+
if (recordInitialValue) {
|
|
743
|
+
// read initial value
|
|
744
|
+
const dataValue = this.node.readAttribute(context, this.itemToMonitor.attributeId);
|
|
745
|
+
this.recordValue(dataValue, true);
|
|
746
|
+
}
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
if (this.samplingInterval === 0) {
|
|
750
|
+
// we have a exception-based dataItem : event based model, so we do not need a timer
|
|
751
|
+
// rather , we setup the "value_changed_event";
|
|
752
|
+
this._value_changed_callback = this._on_value_changed.bind(this);
|
|
753
|
+
this._semantic_changed_callback = this._on_semantic_changed.bind(this);
|
|
754
|
+
this.node.on("value_changed", this._value_changed_callback);
|
|
755
|
+
this.node.on("semantic_changed", this._semantic_changed_callback);
|
|
756
|
+
// initiate first read
|
|
757
|
+
if (recordInitialValue) {
|
|
758
|
+
/* await */ new Promise((resolve) => {
|
|
759
|
+
this.node.readValueAsync(context, (err, dataValue) => {
|
|
760
|
+
if (!err && dataValue) {
|
|
761
|
+
this.recordValue(dataValue, true);
|
|
762
|
+
}
|
|
763
|
+
resolve();
|
|
764
|
+
});
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
else {
|
|
769
|
+
this._set_timer();
|
|
770
|
+
if (recordInitialValue) {
|
|
771
|
+
setImmediate(() => {
|
|
772
|
+
this._on_sampling_timer();
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
_set_parameters(monitoredParameters) {
|
|
778
|
+
_validate_parameters(monitoredParameters);
|
|
779
|
+
// only change clientHandle if it is valid (0<X<MAX)
|
|
780
|
+
if (monitoredParameters.clientHandle !== 0 && monitoredParameters.clientHandle !== 4294967295) {
|
|
781
|
+
this.clientHandle = monitoredParameters.clientHandle;
|
|
782
|
+
}
|
|
783
|
+
// The Server may support data that is collected based on a sampling model or generated based on an
|
|
784
|
+
// exception-based model. The fastest supported sampling interval may be equal to 0, which indicates
|
|
785
|
+
// that the data item is exception-based rather than being sampled at some period. An exception-based
|
|
786
|
+
// model means that the underlying system does not require sampling and reports data changes.
|
|
787
|
+
if (this.node && this.node.nodeClass === node_opcua_data_model_1.NodeClass.Variable) {
|
|
788
|
+
this.samplingInterval = _adjust_sampling_interval(monitoredParameters.samplingInterval, this.node ? this.node.minimumSamplingInterval : 0);
|
|
789
|
+
}
|
|
790
|
+
else {
|
|
791
|
+
this.samplingInterval = _adjust_sampling_interval(monitoredParameters.samplingInterval, 0);
|
|
792
|
+
}
|
|
793
|
+
this.discardOldest = monitoredParameters.discardOldest;
|
|
794
|
+
this.queueSize = _adjust_queue_size(monitoredParameters.queueSize);
|
|
795
|
+
// change filter
|
|
796
|
+
this.filter = monitoredParameters.filter || null;
|
|
797
|
+
}
|
|
798
|
+
_setOverflowBit(notification) {
|
|
799
|
+
if (Object.prototype.hasOwnProperty.call(notification, "value")) {
|
|
800
|
+
(0, node_opcua_assert_1.assert)(notification.value.statusCode.equals(node_opcua_status_code_1.StatusCodes.Good));
|
|
801
|
+
notification.value.statusCode = node_opcua_status_code_1.StatusCode.makeStatusCode(notification.value.statusCode, "Overflow | InfoTypeDataValue");
|
|
802
|
+
(0, node_opcua_assert_1.assert)((0, node_opcua_data_value_1.sameStatusCode)(notification.value.statusCode, node_opcua_status_code_1.StatusCodes.GoodWithOverflowBit));
|
|
803
|
+
(0, node_opcua_assert_1.assert)(notification.value.statusCode.hasOverflowBit);
|
|
804
|
+
}
|
|
805
|
+
// console.log(chalk.cyan("Setting Over"), !!this.$subscription, !!this.$subscription!.subscriptionDiagnostics);
|
|
806
|
+
if (this.$subscription && this.$subscription.subscriptionDiagnostics) {
|
|
807
|
+
this.$subscription.subscriptionDiagnostics.monitoringQueueOverflowCount++;
|
|
808
|
+
}
|
|
809
|
+
// to do eventQueueOverFlowCount
|
|
810
|
+
}
|
|
811
|
+
_enqueue_notification(notification) {
|
|
812
|
+
if (this.queueSize === 1) {
|
|
813
|
+
// https://reference.opcfoundation.org/v104/Core/docs/Part4/5.12.1/#5.12.1.5
|
|
814
|
+
// If the queue size is one, the queue becomes a buffer that always contains the newest
|
|
815
|
+
// Notification. In this case, if the sampling interval of the MonitoredItem is faster
|
|
816
|
+
// than the publishing interval of the Subscription, the MonitoredItem will be over
|
|
817
|
+
// sampling and the Client will always receive the most up-to-date value.
|
|
818
|
+
// The discard policy is ignored if the queue size is one.
|
|
819
|
+
// ensure queue size
|
|
820
|
+
if (!this.queue || this.queue.length !== 1) {
|
|
821
|
+
this.queue = [];
|
|
822
|
+
}
|
|
823
|
+
this.queue[0] = notification;
|
|
824
|
+
(0, node_opcua_assert_1.assert)(this.queue.length === 1);
|
|
825
|
+
}
|
|
826
|
+
else {
|
|
827
|
+
if (this.discardOldest) {
|
|
828
|
+
// push new value to queue
|
|
829
|
+
this.queue.push(notification);
|
|
830
|
+
if (this.queue.length > this.queueSize) {
|
|
831
|
+
this.overflow = true;
|
|
832
|
+
this.queue.shift(); // remove front element
|
|
833
|
+
// set overflow bit
|
|
834
|
+
this._setOverflowBit(this.queue[0]);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
else {
|
|
838
|
+
if (this.queue.length < this.queueSize) {
|
|
839
|
+
this.queue.push(notification);
|
|
840
|
+
}
|
|
841
|
+
else {
|
|
842
|
+
this.overflow = true;
|
|
843
|
+
this._setOverflowBit(notification);
|
|
844
|
+
this.queue[this.queue.length - 1] = notification;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
(0, node_opcua_assert_1.assert)(this.queue.length >= 1);
|
|
849
|
+
}
|
|
850
|
+
_makeDataChangeNotification(dataValue) {
|
|
851
|
+
if (this.clientHandle === -1 || this.clientHandle === 4294967295) {
|
|
852
|
+
debugLog("Invalid client handle");
|
|
853
|
+
}
|
|
854
|
+
const attributeId = this.itemToMonitor.attributeId;
|
|
855
|
+
// if dataFilter is specified ....
|
|
856
|
+
if (this.filter && this.filter instanceof node_opcua_service_subscription_2.DataChangeFilter) {
|
|
857
|
+
if (this.filter.trigger === node_opcua_service_subscription_2.DataChangeTrigger.Status) {
|
|
858
|
+
/** */
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
dataValue = (0, node_opcua_data_value_1.apply_timestamps)(dataValue, this.timestampsToReturn, attributeId);
|
|
862
|
+
return new node_opcua_service_subscription_1.MonitoredItemNotification({
|
|
863
|
+
clientHandle: this.clientHandle,
|
|
864
|
+
value: dataValue
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* @method _enqueue_value
|
|
869
|
+
* @param dataValue {DataValue} the dataValue to enqueue
|
|
870
|
+
* @private
|
|
871
|
+
*/
|
|
872
|
+
_enqueue_value(dataValue) {
|
|
873
|
+
// preconditions:
|
|
874
|
+
if (doDebug) {
|
|
875
|
+
debugLog("_enqueue_value = ", dataValue.toString());
|
|
876
|
+
}
|
|
877
|
+
(0, node_opcua_assert_1.assert)(dataValue instanceof node_opcua_data_value_1.DataValue);
|
|
878
|
+
// lets verify that, if status code is good then we have a valid Variant in the dataValue
|
|
879
|
+
(0, node_opcua_assert_1.assert)(!isGoodish(dataValue.statusCode) || dataValue.value instanceof node_opcua_variant_1.Variant);
|
|
880
|
+
// xx assert(isGoodish(dataValue.statusCode) || util.isNullOrUndefined(dataValue.value) );
|
|
881
|
+
// let's check that data Value is really a different object
|
|
882
|
+
// we may end up with corrupted queue if dataValue are recycled and stored as is in notifications
|
|
883
|
+
(0, node_opcua_assert_1.assert)(dataValue !== this.oldDataValue, "dataValue cannot be the same object twice!");
|
|
884
|
+
// Xx // todo ERN !!!! PLEASE CHECK this !!!
|
|
885
|
+
// Xx // let make a clone, so we have a snapshot
|
|
886
|
+
// Xx dataValue = dataValue.clone();
|
|
887
|
+
// let's check that data Value is really a different object
|
|
888
|
+
// we may end up with corrupted queue if dataValue are recycled and stored as is in notifications
|
|
889
|
+
(0, node_opcua_assert_1.assert)(!this.oldDataValue || !dataValue.value || dataValue.value !== this.oldDataValue.value, "dataValue cannot be the same object twice!");
|
|
890
|
+
if (!(!this.oldDataValue ||
|
|
891
|
+
!this.oldDataValue.value ||
|
|
892
|
+
!dataValue.value ||
|
|
893
|
+
!(dataValue.value.value instanceof Object) ||
|
|
894
|
+
dataValue.value.value !== this.oldDataValue.value.value) &&
|
|
895
|
+
!(dataValue.value.value instanceof Date)) {
|
|
896
|
+
throw new Error("dataValue.value.value cannot be the same object twice! " +
|
|
897
|
+
this.node.browseName.toString() +
|
|
898
|
+
" " +
|
|
899
|
+
dataValue.toString() +
|
|
900
|
+
" " +
|
|
901
|
+
chalk.cyan(this.oldDataValue.toString()));
|
|
902
|
+
}
|
|
903
|
+
// istanbul ignore next
|
|
904
|
+
if (doDebug) {
|
|
905
|
+
debugLog("MonitoredItem#_enqueue_value", this.node.nodeId.toString());
|
|
906
|
+
}
|
|
907
|
+
this.oldDataValue = dataValue;
|
|
908
|
+
const notification = this._makeDataChangeNotification(dataValue);
|
|
909
|
+
this._enqueue_notification(notification);
|
|
910
|
+
}
|
|
911
|
+
_makeEventFieldList(eventFields) {
|
|
912
|
+
(0, node_opcua_assert_1.assert)(Array.isArray(eventFields));
|
|
913
|
+
return new node_opcua_types_1.EventFieldList({
|
|
914
|
+
clientHandle: this.clientHandle,
|
|
915
|
+
eventFields
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
_enqueue_event(eventFields) {
|
|
919
|
+
if (doDebug) {
|
|
920
|
+
debugLog(" MonitoredItem#_enqueue_event");
|
|
921
|
+
}
|
|
922
|
+
const notification = this._makeEventFieldList(eventFields);
|
|
923
|
+
this._enqueue_notification(notification);
|
|
924
|
+
}
|
|
925
|
+
_empty_queue() {
|
|
926
|
+
// empty queue
|
|
927
|
+
this.queue = [];
|
|
928
|
+
this.overflow = false;
|
|
929
|
+
}
|
|
930
|
+
_clear_timer() {
|
|
931
|
+
if (this._samplingId) {
|
|
932
|
+
if (useCommonTimer) {
|
|
933
|
+
(0, node_sampler_1.removeFromTimer)(this);
|
|
934
|
+
}
|
|
935
|
+
else {
|
|
936
|
+
clearInterval(this._samplingId);
|
|
937
|
+
}
|
|
938
|
+
this._samplingId = undefined;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
_set_timer() {
|
|
942
|
+
(0, node_opcua_assert_1.assert)(this.samplingInterval >= MonitoredItem.minimumSamplingInterval);
|
|
943
|
+
(0, node_opcua_assert_1.assert)(!this._samplingId);
|
|
944
|
+
if (useCommonTimer) {
|
|
945
|
+
this._samplingId = (0, node_sampler_1.appendToTimer)(this);
|
|
946
|
+
}
|
|
947
|
+
else {
|
|
948
|
+
// settle periodic sampling
|
|
949
|
+
this._samplingId = setInterval(() => {
|
|
950
|
+
this._on_sampling_timer();
|
|
951
|
+
}, this.samplingInterval);
|
|
952
|
+
}
|
|
953
|
+
// xx console.log("MonitoredItem#_set_timer",this._samplingId);
|
|
954
|
+
}
|
|
955
|
+
_adjust_queue_to_match_new_queue_size() {
|
|
956
|
+
// adjust queue size if necessary
|
|
957
|
+
if (this.queueSize < this.queue.length) {
|
|
958
|
+
if (this.discardOldest) {
|
|
959
|
+
this.queue.splice(0, this.queue.length - this.queueSize);
|
|
960
|
+
}
|
|
961
|
+
else {
|
|
962
|
+
const lastElement = this.queue[this.queue.length - 1];
|
|
963
|
+
// only keep queueSize first element, discard others
|
|
964
|
+
this.queue.splice(this.queueSize);
|
|
965
|
+
this.queue[this.queue.length - 1] = lastElement;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
if (this.queueSize <= 1) {
|
|
969
|
+
this.overflow = false;
|
|
970
|
+
// unset OverFlowBit
|
|
971
|
+
if (this.queue.length === 1) {
|
|
972
|
+
if (this.queue[0] instanceof node_opcua_service_subscription_1.MonitoredItemNotification) {
|
|
973
|
+
const el = this.queue[0];
|
|
974
|
+
if (el.value.statusCode.hasOverflowBit) {
|
|
975
|
+
el.value.statusCode.unset("Overflow | InfoTypeDataValue");
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
(0, node_opcua_assert_1.assert)(this.queue.length <= this.queueSize);
|
|
981
|
+
}
|
|
982
|
+
_adjust_sampling(old_samplingInterval) {
|
|
983
|
+
if (old_samplingInterval !== this.samplingInterval) {
|
|
984
|
+
this._start_sampling(false);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
_on_node_disposed(node) {
|
|
988
|
+
this._on_value_changed(new node_opcua_data_value_1.DataValue({
|
|
989
|
+
sourceTimestamp: new Date(),
|
|
990
|
+
statusCode: node_opcua_status_code_1.StatusCodes.BadNodeIdInvalid
|
|
991
|
+
}));
|
|
992
|
+
this._stop_sampling();
|
|
993
|
+
node.removeListener("dispose", this._on_node_disposed_listener);
|
|
994
|
+
this._on_node_disposed_listener = null;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
exports.MonitoredItem = MonitoredItem;
|
|
998
|
+
MonitoredItem.registry = new node_opcua_object_registry_1.ObjectRegistry();
|
|
999
|
+
MonitoredItem.minimumSamplingInterval = 50; // 50 ms as a minimum sampling interval
|
|
1000
|
+
MonitoredItem.defaultSamplingInterval = 1500; // 1500 ms as a default sampling interval
|
|
1001
|
+
MonitoredItem.maximumSamplingInterval = 1000 * 60 * 60; // 1 hour !
|
|
1002
1002
|
//# sourceMappingURL=monitored_item.js.map
|