• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

node-opcua / node-opcua / 28389643215

29 Jun 2026 05:10PM UTC coverage: 92.054%. Remained the same
28389643215

push

github

erossignon
fix(filter): return a StatusCode when a selectClause typeDefinitionId does not resolve

checkSelectClause now returns BadNodeIdUnknown when the SimpleAttributeOperand
typeDefinitionId does not reference an existing node, and BadTypeMismatch when it
references a node that is not an ObjectType, instead of dereferencing the result
of findEventType() (which returns null for an unknown NodeId).

Adds a unit test (checkSelectClauses with an unresolved typeDefinitionId) and an
end-to-end test verifying the selectClause result is reported per-clause.

18511 of 21825 branches covered (84.82%)

5 of 6 new or added lines in 1 file covered. (83.33%)

340 existing lines in 8 files now uncovered.

164590 of 178797 relevant lines covered (92.05%)

434180.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.55
/packages/node-opcua-server/source/monitored_item.ts
1
/**
418✔
2
 * @module node-opcua-server
2✔
3
 */
2✔
4

2✔
5
import chalk from "chalk";
2✔
6
import { EventEmitter } from "events";
2✔
7
import {
2✔
8
    type AddressSpace,
2✔
9
    type BaseNode,
2✔
10
    type IEventData,
2✔
11
    makeAttributeEventName,
2✔
12
    SessionContext,
2✔
13
    type UAVariable
2✔
14
} from "node-opcua-address-space";
2✔
15
import type { ISessionContext, UAMethod, UAObject } from "node-opcua-address-space-base";
2✔
16
import { assert } from "node-opcua-assert";
2✔
17
import type { DateTime, UInt32 } from "node-opcua-basic-types";
2✔
18
import { AttributeIds, NodeClass, type QualifiedNameOptions } from "node-opcua-data-model";
2✔
19
import {
2✔
20
    apply_timestamps,
2✔
21
    coerceTimestampsToReturn,
2✔
22
    DataValue,
2✔
23
    extractRange,
2✔
24
    sameDataValue,
2✔
25
    sameStatusCode
2✔
26
} from "node-opcua-data-value";
2✔
27
import { checkDebugFlag, make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug";
2✔
28
import type { ExtensionObject } from "node-opcua-extension-object";
2✔
29
import type { NodeId } from "node-opcua-nodeid";
2✔
30
import { type NumericalRange0, NumericRange } from "node-opcua-numeric-range";
2✔
31
import { ObjectRegistry } from "node-opcua-object-registry";
2✔
32
import { EventFilter, extractEventFields } from "node-opcua-service-filter";
2✔
33
import { ReadValueId, type TimestampsToReturn } from "node-opcua-service-read";
2✔
34
import {
2✔
35
    DataChangeFilter,
2✔
36
    DataChangeTrigger,
2✔
37
    DeadbandType,
2✔
38
    isOutsideDeadbandAbsolute,
2✔
39
    isOutsideDeadbandNone,
2✔
40
    isOutsideDeadbandPercent,
2✔
41
    MonitoredItemModifyResult,
2✔
42
    MonitoredItemNotification,
2✔
43
    MonitoringMode,
2✔
44
    MonitoringParameters,
2✔
45
    type PseudoRange
2✔
46
} from "node-opcua-service-subscription";
2✔
47
import { type CallbackT, StatusCode, StatusCodes } from "node-opcua-status-code";
2✔
48
import {
2✔
49
    DataChangeNotification,
2✔
50
    EventFieldList,
2✔
51
    EventNotificationList,
2✔
52
    type MonitoringFilter,
2✔
53
    type ReadValueIdOptions,
2✔
54
    type SimpleAttributeOperand,
2✔
55
    type SubscriptionDiagnosticsDataType
2✔
56
} from "node-opcua-types";
2✔
57
import { sameVariant, Variant, VariantArrayType } from "node-opcua-variant";
2✔
58
import { checkWhereClauseOnAdressSpace as checkWhereClauseOnAddressSpace } from "./filter/check_where_clause_on_address_space";
2✔
59
import { appendToTimer, removeFromTimer } from "./node_sampler";
2✔
60
import type { SamplingFunc } from "./sampling_func";
2✔
61
import type { MonitoredItemBase } from "./server_subscription";
2✔
62
import { validateFilter } from "./validate_filter";
2✔
63

2✔
64
const errorLog = make_errorLog(__filename);
2✔
65

2✔
66
export type QueueItem = MonitoredItemNotification | EventFieldList;
2✔
67

2✔
68
const defaultItemToMonitor: ReadValueIdOptions = new ReadValueId({
2✔
69
    attributeId: AttributeIds.Value,
2✔
70
    indexRange: undefined
2✔
71
});
2✔
72

2✔
73
const debugLog = make_debugLog(__filename);
2✔
74
const doDebug = checkDebugFlag(__filename);
2✔
75
const doDebug2 = doDebug && false;
2!
76
const warningLog = make_warningLog(__filename);
2✔
77

2✔
78
function _adjust_sampling_interval(samplingInterval: number, node_minimumSamplingInterval: number): number {
21,445✔
79
    assert(typeof node_minimumSamplingInterval === "number", "expecting a number");
21,445✔
80

21,445✔
81
    if (samplingInterval === 0) {
21,445✔
82
        return node_minimumSamplingInterval === 0
427✔
83
            ? samplingInterval
427✔
84
            : Math.max(MonitoredItem.minimumSamplingInterval, node_minimumSamplingInterval);
414✔
85
    }
427✔
86
    assert(samplingInterval >= 0, " this case should have been prevented outside");
21,018✔
87
    samplingInterval = samplingInterval || MonitoredItem.defaultSamplingInterval;
21,445!
88
    samplingInterval = Math.max(samplingInterval, MonitoredItem.minimumSamplingInterval);
21,445✔
89
    samplingInterval = Math.min(samplingInterval, MonitoredItem.maximumSamplingInterval);
21,445✔
90
    samplingInterval =
21,445✔
91
        node_minimumSamplingInterval === 0 ? samplingInterval : Math.max(samplingInterval, node_minimumSamplingInterval);
21,445✔
92

21,445✔
93
    return samplingInterval;
21,445✔
94
}
21,445✔
95

2✔
96
const maxQueueSize = 5000;
2✔
97

2✔
98
function _adjust_queue_size(queueSize: number): number {
21,445✔
99
    queueSize = Math.min(queueSize, maxQueueSize);
21,445✔
100
    queueSize = Math.max(1, queueSize);
21,445✔
101
    return queueSize;
21,445✔
102
}
21,445✔
103

2✔
104
function _validate_parameters(monitoringParameters: any) {
21,445✔
105
    // xx assert(options instanceof MonitoringParameters);
21,445✔
106
    assert(Object.hasOwn(monitoringParameters, "clientHandle"));
21,445✔
107
    assert(Object.hasOwn(monitoringParameters, "samplingInterval"));
21,445✔
108
    assert(isFinite(monitoringParameters.clientHandle));
21,445✔
109
    assert(isFinite(monitoringParameters.samplingInterval));
21,445✔
110
    assert(typeof monitoringParameters.discardOldest === "boolean");
21,445✔
111
    assert(isFinite(monitoringParameters.queueSize));
21,445✔
112
    assert(monitoringParameters.queueSize >= 0);
21,445✔
113
}
21,445✔
114

2✔
115
function statusCodeHasChanged(newDataValue: DataValue, oldDataValue: DataValue): boolean {
109✔
116
    assert(newDataValue instanceof DataValue);
109✔
117
    assert(oldDataValue instanceof DataValue);
109✔
118
    return newDataValue.statusCode.value !== oldDataValue.statusCode.value;
109✔
119
}
109✔
120

2✔
121
function valueHasChanged(
94✔
122
    this: MonitoredItem,
94✔
123
    newDataValue: DataValue,
94✔
124
    oldDataValue: DataValue,
94✔
125
    deadbandType: DeadbandType,
94✔
126
    deadbandValue: number
94✔
127
): boolean {
94✔
128
    assert(newDataValue instanceof DataValue);
94✔
129
    assert(oldDataValue instanceof DataValue);
94✔
130
    switch (deadbandType) {
94✔
131
        case DeadbandType.None:
94✔
132
            assert(newDataValue.value instanceof Variant);
4✔
133
            assert(newDataValue.value instanceof Variant);
4✔
134
            // No Deadband calculation should be applied.
4✔
135
            return isOutsideDeadbandNone(oldDataValue.value, newDataValue.value);
4✔
136
        case DeadbandType.Absolute:
94✔
137
            // AbsoluteDeadband
66✔
138
            return isOutsideDeadbandAbsolute(oldDataValue.value, newDataValue.value, deadbandValue);
66✔
139
        default: {
94✔
140
            // Percent_2    PercentDeadband (This type is specified in Part 8).
24✔
141
            assert(deadbandType === DeadbandType.Percent);
24✔
142

24✔
143
            // The range of the deadbandValue is from 0.0 to 100.0 Percent.
24✔
144
            assert(deadbandValue >= 0 && deadbandValue <= 100);
24✔
145

24✔
146
            // DeadbandType = PercentDeadband
24✔
147
            // For this type of deadband the deadbandValue is defined as the percentage of the EURange. That is,
24✔
148
            // it applies only to AnalogItems with an EURange Property that defines the typical value range for the
24✔
149
            // item. This range shall be multiplied with the deadbandValue and then compared to the actual value change
24✔
150
            // to determine the need for a data change notification. The following pseudo code shows how the deadband
24✔
151
            // is calculated:
24✔
152
            //      DataChange if (absolute value of (last cached value - current value) >
24✔
153
            //                                          (deadbandValue/100.0) * ((high-low) of EURange)))
24✔
154
            //
24✔
155
            // StatusCode BadDeadbandFilterInvalid (see Table 27).
24✔
156
            // If the Value of the MonitoredItem is an array, then the deadband calculation logic shall be applied to
24✔
157
            // each element of the array. If an element that requires a DataChange is found, then no further
24✔
158
            // deadband checking is necessary and the entire array shall be returned.
24✔
159
            assert(this.node !== null, "expecting a valid address_space object here to get access the the EURange");
24✔
160

24✔
161
            const euRangeNode = this.node!.getChildByName("EURange") as UAVariable;
24✔
162
            if (euRangeNode && euRangeNode.nodeClass === NodeClass.Variable) {
24✔
163
                // double,double
24✔
164
                const rangeVariant = euRangeNode.readValue().value;
24✔
165
                return isOutsideDeadbandPercent(
24✔
166
                    oldDataValue.value,
24✔
167
                    newDataValue.value,
24✔
168
                    deadbandValue,
24✔
169
                    rangeVariant.value as PseudoRange
24✔
170
                );
24✔
171
            } else {
24!
172
                errorLog("EURange is not of type Variable");
×
173
            }
×
174
            return true;
×
175
        }
×
176
    }
94✔
177
}
94✔
178

2✔
179
function timestampHasChanged(t1: DateTime, t2: DateTime): boolean {
22✔
180
    if (t1 || !t2 || t2 || !t1) {
22!
181
        return true;
22✔
182
    }
22✔
183
    if (!t1 || !t2) {
22!
184
        return false;
×
185
    }
×
186
    return (t1 as Date).getTime() !== (t2 as Date).getTime();
×
187
}
3✔
188

2✔
189
function apply_dataChange_filter(this: MonitoredItem, newDataValue: DataValue, oldDataValue: DataValue): boolean {
131✔
190
    /* c8 ignore next */
2✔
191
    if (!this.filter || !(this.filter instanceof DataChangeFilter)) {
2✔
192
        throw new Error("Internal Error");
×
193
    }
×
194

131✔
195
    const trigger = this.filter.trigger;
131✔
196
    // c8 ignore next
131✔
197
    if (doDebug) {
131!
198
        try {
×
199
            debugLog("filter pass ?", DataChangeTrigger[trigger], this.oldDataValue?.toString(), newDataValue.toString());
×
200
            if (
×
201
                trigger === DataChangeTrigger.Status ||
×
202
                trigger === DataChangeTrigger.StatusValue ||
×
203
                trigger === DataChangeTrigger.StatusValueTimestamp
×
204
            ) {
×
205
                debugLog("statusCodeHasChanged ", statusCodeHasChanged(newDataValue, oldDataValue));
×
206
            }
×
207
            if (trigger === DataChangeTrigger.StatusValue || trigger === DataChangeTrigger.StatusValueTimestamp) {
×
208
                debugLog(
×
209
                    "valueHasChanged ",
×
210
                    valueHasChanged.call(this, newDataValue, oldDataValue, this.filter!.deadbandType, this.filter!.deadbandValue)
×
211
                );
×
212
            }
×
213
            if (trigger === DataChangeTrigger.StatusValueTimestamp) {
×
214
                debugLog("timestampHasChanged ", timestampHasChanged(newDataValue.sourceTimestamp, oldDataValue.sourceTimestamp));
×
215
            }
×
216
        } catch (err) {
×
217
            warningLog(err);
×
218
        }
×
219
    }
×
220
    switch (trigger) {
131✔
221
        case DataChangeTrigger.Status: {
131✔
222
            //
5✔
223
            //              Status
5✔
224
            //              Report a notification ONLY if the StatusCode associated with
5✔
225
            //              the value changes. See Table 166 for StatusCodes defined in
5✔
226
            //              this standard. Part 8 specifies additional StatusCodes that are
5✔
227
            //              valid in particular for device data.
5✔
228
            return statusCodeHasChanged(newDataValue, oldDataValue);
5✔
229
        }
5✔
230
        case DataChangeTrigger.StatusValue: {
131✔
231
            //              filtering value changes.
104✔
232
            //              change. The Deadband filter can be used in addition for
104✔
233
            //              Report a notification if either the StatusCode or the value
104✔
234
            //              StatusValue
104✔
235
            //              This is the default setting if no filter is set.
104✔
236
            return (
104✔
237
                statusCodeHasChanged(newDataValue, oldDataValue) ||
104✔
238
                valueHasChanged.call(this, newDataValue, oldDataValue, this.filter.deadbandType, this.filter.deadbandValue)
94✔
239
            );
104✔
240
        }
104✔
241
        default: {
131✔
242
            // StatusValueTimestamp
22✔
243
            //              Report a notification if either StatusCode, value or the
22✔
244
            //              SourceTimestamp change.
22✔
245
            //
22✔
246
            //              If a Deadband filter is specified,this trigger has the same behavior as STATUS_VALUE_1.
22✔
247
            //
22✔
248
            //              If the DataChangeFilter is not applied to the monitored item, STATUS_VALUE_1
22✔
249
            //              is the default reporting behavior
22✔
250
            assert(trigger === DataChangeTrigger.StatusValueTimestamp);
22✔
251
            return (
22✔
252
                timestampHasChanged(newDataValue.sourceTimestamp, oldDataValue.sourceTimestamp) ||
22!
253
                statusCodeHasChanged(newDataValue, oldDataValue) ||
19!
254
                valueHasChanged.call(this, newDataValue, oldDataValue, this.filter.deadbandType, this.filter.deadbandValue)
×
255
            );
22✔
256
        }
22✔
257
    }
131✔
258
}
131✔
259

2✔
260
const s = (a: any) => JSON.stringify(a, null, "  ");
2✔
261

2✔
262
function safeGuardRegister(monitoredItem: MonitoredItem) {
×
263
    (monitoredItem.oldDataValue as any)._$monitoredItem = monitoredItem.node?.nodeId?.toString();
×
264
    (monitoredItem as any)._$safeGuard = s((monitoredItem as any).oldDataValue);
×
265
}
×
266
function safeGuardVerify(monitoredItem: MonitoredItem) {
×
267
    if ((monitoredItem as any)._$safeGuard) {
×
268
        const verif = s(monitoredItem.oldDataValue || "");
×
269
        if (verif !== (monitoredItem as any)._$safeGuard) {
×
270
            errorLog(verif, (monitoredItem as any)._$safeGuard);
×
271
            throw new Error("Internal error: DataValue has been altered !!!");
×
272
        }
×
273
    }
×
274
}
×
275
function apply_filter(this: MonitoredItem, newDataValue: DataValue) {
14,811✔
276
    if (this.oldDataValue === badDataUnavailable) {
14,811✔
277
        return true; // keep
6,665✔
278
    }
6,665✔
279

8,153✔
280
    // c8 ignore next
8,153✔
281
    doDebug && safeGuardVerify(this);
14,811!
282

14,811✔
283
    if (this.filter instanceof DataChangeFilter) {
14,811✔
284
        return apply_dataChange_filter.call(this, newDataValue, this.oldDataValue);
131✔
285
    } else {
14,811✔
286
        // if filter not set, by default report changes to Status or Value only
8,015✔
287
        if (newDataValue.statusCode.value !== this.oldDataValue.statusCode.value) {
8,015✔
288
            return true; // Keep because statusCode has changed ...
32✔
289
        }
32✔
290
        return !sameVariant(newDataValue.value, this.oldDataValue.value);
7,987✔
291
    }
7,983✔
292
}
14,811✔
293

2✔
294
function setSemanticChangeBit(notification: QueueItem | DataValue): void {
4✔
295
    if (notification instanceof MonitoredItemNotification) {
4!
296
        notification.value.statusCode = StatusCode.makeStatusCode(
×
297
            notification.value.statusCode || StatusCodes.Good,
×
298
            "SemanticChanged"
×
299
        );
×
300
    } else if (notification instanceof DataValue) {
4✔
301
        notification.statusCode = StatusCode.makeStatusCode(notification.statusCode || StatusCodes.Good, "SemanticChanged");
4!
302
    }
4✔
303
}
4✔
304

2✔
305
const useCommonTimer = true;
2✔
306

2✔
307
export interface MonitoredItemOptions extends MonitoringParameters {
2✔
308
    monitoringMode: MonitoringMode;
2✔
309
    /**
2✔
310
     * the monitoredItem Id assigned by the server to this monitoredItem.
2✔
311
     */
2✔
312
    monitoredItemId: number;
2✔
313
    itemToMonitor?: ReadValueIdOptions;
2✔
314
    timestampsToReturn?: TimestampsToReturn;
2✔
315

2✔
316
    // MonitoringParameters
2✔
317
    filter: ExtensionObject | null;
2✔
318
    /**
2✔
319
     * if discardOldest === true, older items are removed from the queue when queue overflows
2✔
320
     */
2✔
321
    discardOldest: boolean;
2✔
322
    /**
2✔
323
     * the size of the queue.
2✔
324
     */
2✔
325
    queueSize: number;
2✔
326
    /**
2✔
327
     * the monitored item sampling interval ..
2✔
328
     */
2✔
329
    samplingInterval: number;
2✔
330
    /**
2✔
331
     * the client handle
2✔
332
     */
2✔
333
    clientHandle: number;
2✔
334
}
2✔
335

2✔
336
export interface BaseNode2 extends EventEmitter {
2✔
337
    nodeId: NodeId;
2✔
338
    browseName: QualifiedNameOptions;
2✔
339
    nodeClass: NodeClass;
2✔
340
    dataType: NodeId;
2✔
341
    addressSpace: any;
2✔
342

2✔
343
    readAttribute(context: SessionContext | null, attributeId: AttributeIds): DataValue;
2✔
344
}
2✔
345

2✔
346
type TimerKey = NodeJS.Timer;
2✔
347

2✔
348
export interface IServerSession2 {
2✔
349
    sessionContext: ISessionContext;
2✔
350
}
2✔
351

2✔
352
export interface ISubscription {
2✔
353
    $session?: IServerSession2;
2✔
354
    subscriptionDiagnostics: SubscriptionDiagnosticsDataType;
2✔
355
    getMonitoredItem(monitoredItemId: number): MonitoredItem | null;
2✔
356
    // ServerCapabilities filter limits (OPC UA Part 5), used to validate EventFilters on modify
2✔
357
    readonly maxWhereClauseParameters?: number;
2✔
358
    readonly maxSelectClauseParameters?: number;
2✔
359
}
2✔
360

2✔
361
function isSourceNewerThan(a: DataValue, b?: DataValue): boolean {
×
362
    if (!b) {
×
363
        return true;
×
364
    }
×
365
    const at = a.sourceTimestamp?.getTime() || 0;
×
366
    const bt = b.sourceTimestamp?.getTime() || 0;
×
367

×
368
    if (at === bt) {
×
369
        return a.sourcePicoseconds > b.sourcePicoseconds;
×
UNCOV
370
    }
×
UNCOV
371
    return at > bt;
×
UNCOV
372
}
×
373

2✔
374
const badDataUnavailable = new DataValue({ statusCode: StatusCodes.BadDataUnavailable }); // unset initially
2✔
375
/**
2✔
376
 * a server side monitored item
2✔
377
 *
2✔
378
 * - Once created, the MonitoredItem will raised an "samplingEvent" event every "samplingInterval" millisecond
2✔
379
 *   until {{#crossLink "MonitoredItem/terminate:method"}}{{/crossLink}} is called.
2✔
380
 *
2✔
381
 * - It is up to the  event receiver to call {{#crossLink "MonitoredItem/recordValue:method"}}{{/crossLink}}.
2✔
382
 *
2✔
383
 */
2✔
384
export class MonitoredItem extends EventEmitter implements MonitoredItemBase {
2✔
385
    public get node(): UAVariable | UAObject | UAMethod | null {
2✔
386
        return this._node;
661,161✔
387
    }
661,161✔
388

2✔
389
    public set node(someNode: UAVariable | UAObject | UAMethod | null) {
2✔
UNCOV
390
        throw new Error("Unexpected way to set node");
×
UNCOV
391
    }
×
392

2✔
393
    public static registry = new ObjectRegistry();
2✔
394
    public static minimumSamplingInterval = 50; // 50 ms as a minimum sampling interval
2✔
395
    public static defaultSamplingInterval = 1500; // 1500 ms as a default sampling interval
2✔
396
    public static maximumSamplingInterval = 1000 * 60 * 60; // 1 hour !
2✔
397

2✔
398
    public samplingInterval = -1;
2✔
399
    public monitoredItemId: number;
2✔
400
    public overflow: boolean;
2✔
401
    public oldDataValue: DataValue;
2✔
402

2✔
403
    #monitoringMode: MonitoringMode = MonitoringMode.Invalid;
2✔
404

2✔
405
    public get monitoringMode() {
2✔
406
        return this.#monitoringMode;
910,756✔
407
    }
910,756✔
408

2✔
409
    public timestampsToReturn: TimestampsToReturn;
2✔
410
    public itemToMonitor: any;
2✔
411
    public filter: MonitoringFilter | null;
2✔
412
    public discardOldest = true;
2✔
413
    public queueSize = 0;
2✔
414
    public clientHandle: UInt32;
2✔
415
    public $subscription?: ISubscription;
2✔
416
    public _samplingId?: NodeJS.Timeout | string;
2✔
417
    public samplingFunc: SamplingFunc | null = null;
2✔
418

2✔
419
    private _node: UAVariable | UAObject | UAMethod | null;
2✔
420
    public queue: QueueItem[];
2✔
421
    private _semantic_version: number;
2✔
422
    private _is_sampling = false;
2✔
423
    private _on_opcua_event_received_callback: any;
2✔
424
    private _attribute_changed_callback: any;
2✔
425
    private _value_changed_callback: any;
2✔
426
    private _semantic_changed_callback: any;
2✔
427
    private _on_node_disposed_listener: (() => void) | null;
2✔
428
    private _linkedItems?: number[];
2✔
429
    private _triggeredNotifications?: QueueItem[];
2✔
430

2✔
431
    constructor(options: MonitoredItemOptions) {
2✔
432
        super();
14,771✔
433

14,771✔
434
        assert(Object.hasOwn(options, "monitoredItemId"));
14,771✔
435
        assert(!options.monitoringMode, "use setMonitoring mode explicitly to activate the monitored item");
14,771✔
436

14,771✔
437
        options.itemToMonitor = options.itemToMonitor || defaultItemToMonitor;
14,771✔
438

14,771✔
439
        this._samplingId = undefined;
14,771✔
440
        this.clientHandle = 0; // invalid
14,771✔
441
        this.filter = null;
14,771✔
442
        this._set_parameters(options);
14,771✔
443

14,771✔
444
        this.monitoredItemId = options.monitoredItemId; // ( known as serverHandle)
14,771✔
445

14,771✔
446
        this.queue = [];
14,771✔
447
        this.overflow = false;
14,771✔
448

14,771✔
449
        this.oldDataValue = badDataUnavailable;
14,771✔
450

14,771✔
451
        // user has to call setMonitoringMode
14,771✔
452
        this.#monitoringMode = MonitoringMode.Invalid;
14,771✔
453

14,771✔
454
        this.timestampsToReturn = coerceTimestampsToReturn(options.timestampsToReturn);
14,771✔
455

14,771✔
456
        this.itemToMonitor = options.itemToMonitor;
14,771✔
457

14,771✔
458
        this._node = null;
14,771✔
459
        this._semantic_version = 0;
14,771✔
460

14,771✔
461
        // c8 ignore next
14,771✔
462
        if (doDebug) {
14,771!
UNCOV
463
            debugLog("Monitoring ", options.itemToMonitor.toString());
×
UNCOV
464
        }
×
465

14,771✔
466
        this._on_node_disposed_listener = null;
14,771✔
467

14,771✔
468
        MonitoredItem.registry.register(this);
14,771✔
469
    }
14,771✔
470

2✔
471
    public setNode(node: UAVariable | UAObject | UAMethod): void {
2✔
472
        assert(!this.node || this.node === node, "node already set");
14,771!
473
        this._node = node;
14,771✔
474
        this._semantic_version = (node as any).semantic_version;
14,771✔
475
        this._on_node_disposed_listener = () => this._on_node_disposed(this._node!);
14,771✔
476
        (this._node as BaseNode).on("dispose", this._on_node_disposed_listener);
14,771✔
477
    }
14,771✔
478

2✔
479
    public setMonitoringMode(monitoringMode: MonitoringMode): StatusCode {
2✔
480
        if (monitoringMode === this.monitoringMode) {
14,781✔
481
            // nothing to do
9✔
482
            return StatusCodes.BadNothingToDo;
9✔
483
        }
9✔
484
        if (monitoringMode === MonitoringMode.Invalid) {
14,781✔
485
            return StatusCodes.BadInvalidArgument;
1✔
486
        }
1✔
487
        const old_monitoringMode = this.monitoringMode;
14,776✔
488

14,771✔
489
        if (monitoringMode === MonitoringMode.Disabled) {
14,781✔
490
            this.#monitoringMode = monitoringMode;
15✔
491
            this._stop_sampling();
15✔
492
            // OPCUA 1.03 part 4 : $5.12.4
15✔
493
            // setting the mode to DISABLED causes all queued Notifications to be deleted
15✔
494
            this._empty_queue();
15✔
495
        } else if (monitoringMode === MonitoringMode.Sampling || monitoringMode === MonitoringMode.Reporting) {
14,781✔
496
            this.#monitoringMode = monitoringMode;
14,755✔
497

14,755✔
498
            // OPCUA 1.03 part 4 : $5.12.1.3
14,755✔
499
            // When a MonitoredItem is enabled (i.e. when the MonitoringMode is changed from DISABLED to
14,755✔
500
            // SAMPLING or REPORTING) or it is created in the enabled state, the Server shall report the first
14,755✔
501
            // sample as soon as possible and the time of this sample becomes the starting point for the next
14,755✔
502
            // sampling interval.
14,755✔
503
            const recordInitialValue =
14,755✔
504
                old_monitoringMode === MonitoringMode.Invalid || old_monitoringMode === MonitoringMode.Disabled;
14,755✔
505
            const installEventHandler = old_monitoringMode === MonitoringMode.Invalid;
14,755✔
506
            this._start_sampling(recordInitialValue);
14,755✔
507
        } else {
14,756✔
508
            return StatusCodes.BadInternalError;
1✔
509
        }
1✔
510

14,776✔
511
        return StatusCodes.Good;
14,776✔
512
    }
14,776✔
513

2✔
514
    /*
2✔
515
     * Terminate the  MonitoredItem.
2✔
516
     * This will stop the internal sampling timer.
2✔
517
     */
2✔
518
    public terminate(): void {
2✔
519
        this._stop_sampling();
14,771✔
520
    }
14,771✔
521

2✔
522
    public dispose(): void {
2✔
523
        // c8 ignore next
14,771✔
524
        if (doDebug) {
14,771!
UNCOV
525
            debugLog("DISPOSING MONITORED ITEM", this._node!.nodeId.toString());
×
UNCOV
526
        }
×
527

14,771✔
528
        this._stop_sampling();
14,771✔
529

14,771✔
530
        MonitoredItem.registry.unregister(this);
14,771✔
531

14,771✔
532
        if (this._on_node_disposed_listener) {
14,771✔
533
            (this._node as BaseNode).removeListener("dispose", this._on_node_disposed_listener);
14,770✔
534
            this._on_node_disposed_listener = null;
14,770✔
535
        }
14,770✔
536

14,771✔
537
        // x assert(this._samplingId === null,"Sampling Id must be null");
14,771✔
538
        this.oldDataValue = badDataUnavailable;
14,771✔
539
        this.queue = [];
14,771✔
540
        this.itemToMonitor = null;
14,771✔
541
        this.filter = null;
14,771✔
542
        this.monitoredItemId = 0;
14,771✔
543
        this._node = null;
14,771✔
544
        this._semantic_version = 0;
14,771✔
545

14,771✔
546
        this.$subscription = undefined;
14,771✔
547

14,771✔
548
        this.removeAllListeners();
14,771✔
549

14,771✔
550
        assert(!this._samplingId);
14,771✔
551
        assert(!this._value_changed_callback);
14,771✔
552
        assert(!this._semantic_changed_callback);
14,771✔
553
        assert(!this._attribute_changed_callback);
14,771✔
554
        assert(!this._on_opcua_event_received_callback);
14,771✔
555
        this._on_opcua_event_received_callback = null;
14,771✔
556
        this._attribute_changed_callback = null;
14,771✔
557
        this._semantic_changed_callback = null;
14,771✔
558
        this._on_opcua_event_received_callback = null;
14,771✔
559
    }
14,771✔
560

2✔
561
    public get isSampling(): boolean {
2✔
562
        return (
3✔
563
            !!this._samplingId ||
3✔
564
            typeof this._value_changed_callback === "function" ||
2✔
565
            typeof this._attribute_changed_callback === "function"
2✔
566
        );
3✔
567
    }
3✔
568

2✔
569
    public toJSON(): Record<string, string | number | undefined> {
2✔
570
        return {
×
571
            nodeId: this.node?.nodeId.toString(),
×
572
            samplingInterval: this.samplingInterval,
×
UNCOV
573
            monitoredItemId: this.monitoredItemId
×
UNCOV
574
        };
×
575
    }
×
576

2✔
577
    public toString(): string {
2✔
578
        let str = "";
×
579
        str += `monitored item nodeId : ${this.node?.nodeId.toString()} \n`;
×
580
        str += `    sampling interval : ${this.samplingInterval} \n`;
×
UNCOV
581
        str += `    monitoredItemId   : ${this.monitoredItemId} \n`;
×
UNCOV
582
        return str;
×
583
    }
×
584

2✔
585
    public [Symbol.for("nodejs.util.inspect.custom")](): string {
2✔
UNCOV
586
        return this.toString();
×
UNCOV
587
    }
×
588
    /**
2✔
589
     * @param dataValue       the whole dataValue
2✔
590
     * @param skipChangeTest  indicates whether recordValue should  not check that dataValue is really
2✔
591
     *                                  different from previous one, ( by checking timestamps but also variant value)
2✔
592
     * @private
2✔
593
     *
2✔
594
     * Notes:
2✔
595
     *  - recordValue can only be called within timer event
2✔
596
     *  - for performance reason, dataValue may be a shared value with the underlying node,
2✔
597
     *    therefore recordValue must clone the dataValue to make sure it retains a snapshot
2✔
598
     *    of the contain at the time recordValue was called.
2✔
599
     *
2✔
600
     * return true if the value has been recorded, false if not.
2✔
601
     *
2✔
602
     * Value will not be recorded :
2✔
603
     *   * if the range do not overlap
2✔
604
     *   * is !skipChangeTest and value is equal to previous value
2✔
605
     *
2✔
606
     */
2✔
607
    // eslint-disable-next-line complexity, max-statements
2✔
608
    public recordValue(dataValue: DataValue, skipChangeTest?: boolean, indexRange?: NumericRange): boolean {
2✔
609
        if (!this.itemToMonitor) {
216,248✔
610
            // we must have a valid itemToMonitor(have this monitoredItem been disposed already ?)
1✔
611
            // c8 ignore next
1✔
612
            doDebug && debugLog("recordValue => Rejected", this.node?.browseName.toString(), " because no itemToMonitor");
1!
613
            return false;
1✔
614
        }
1✔
615

216,247✔
616
        if (dataValue === this.oldDataValue) {
216,248!
UNCOV
617
            errorLog("recordValue expects different dataValue to be provided");
×
UNCOV
618
        }
×
619
        doDebug && assert(!dataValue.value || dataValue.value.isValid(), "expecting a valid variant value");
216,248!
620

216,248✔
621
        const hasSemanticChanged = this.node && (this.node as any).semantic_version !== this._semantic_version;
216,248✔
622

216,248✔
623
        if (!hasSemanticChanged && indexRange && this.itemToMonitor.indexRange) {
216,248✔
624
            // we just ignore changes that do not fall within our range
86✔
625
            // ( unless semantic bit has changed )
86✔
626
            if (!NumericRange.overlap(indexRange as NumericalRange0, this.itemToMonitor.indexRange)) {
86✔
627
                // c8 ignore next
3✔
628
                doDebug && debugLog("recordValue => Rejected", this.node?.browseName.toString(), " because no range not overlap");
3!
629
                return false; // no overlap !
3✔
630
            }
3✔
631
        }
86✔
632

216,244✔
633
        // extract the range that we are interested with
216,244✔
634
        dataValue = extractRange(dataValue, this.itemToMonitor.indexRange);
216,244✔
635

216,244✔
636
        // c8 ignore next
216,244✔
637
        if (doDebug2) {
216,248!
638
            debugLog(
×
639
                "MonitoredItem#recordValue",
×
640
                this.node!.nodeId.toString(),
×
641
                this.node!.browseName.toString(),
×
642
                " has Changed = ",
×
643
                !sameDataValue(dataValue, this.oldDataValue!),
×
644
                "skipChangeTest = ",
×
645
                skipChangeTest,
×
646
                "hasSemanticChanged = ",
×
UNCOV
647
                hasSemanticChanged
×
UNCOV
648
            );
×
UNCOV
649
        }
×
650

216,243✔
651
        // if semantic has changed, value need to be enqueued regardless of other assumptions
216,243✔
652
        if (hasSemanticChanged) {
216,248✔
653
            debugLog("_enqueue_value => because hasSemanticChanged");
4✔
654
            setSemanticChangeBit(dataValue);
4✔
655
            this._semantic_version = (this.node as UAVariable).semantic_version;
4✔
656
            this._enqueue_value(dataValue);
4✔
657
            // c8 ignore next
4✔
658
            doDebug && debugLog("recordValue => OK ", this.node?.browseName.toString(), " because hasSemanticChanged");
4!
659
            return true;
4✔
660
        }
4✔
661

216,239✔
662
        const useIndexRange = this.itemToMonitor.indexRange && !this.itemToMonitor.indexRange.isEmpty();
216,248✔
663

216,248✔
664
        if (!skipChangeTest) {
216,248✔
665
            const hasChanged = !sameDataValue(dataValue, this.oldDataValue!);
201,572✔
666
            if (!hasChanged) {
201,572✔
667
                // c8 ignore next
186,761✔
668
                doDebug2 &&
186,761!
669
                    debugLog(
×
UNCOV
670
                        "recordValue => Rejected ",
×
UNCOV
671
                        this.node?.browseName.toString(),
×
UNCOV
672
                        " because !skipChangeTest && sameDataValue"
×
673
                    );
186,641✔
674
                return false;
186,761✔
675
            }
186,761✔
676
        }
201,572✔
677

29,598✔
678
        if (!skipChangeTest && !apply_filter.call(this, dataValue)) {
216,248✔
679
            // c8 ignore next
2,615✔
680
            if (doDebug) {
2,615!
681
                debugLog("recordValue => Rejected ", this.node?.browseName.toString(), " because apply_filter");
×
682
                debugLog("current Value =>", this.oldDataValue?.toString());
×
UNCOV
683
                debugLog("proposed Value =>", dataValue?.toString());
×
UNCOV
684
                debugLog("proposed Value =>", dataValue == this.oldDataValue, dataValue.value === this.oldDataValue?.value);
×
UNCOV
685
            }
×
686
            return false;
2,615✔
687
        }
2,615✔
688

27,021✔
689
        if (useIndexRange) {
216,248✔
690
            // when an indexRange is provided , make sure that no record happens unless
16✔
691
            // extracted variant in the selected range  has really changed.
16✔
692

16✔
693
            // c8 ignore next
16✔
694
            if (doDebug) {
16!
695
                debugLog("Current : ", this.oldDataValue?.toString());
×
UNCOV
696
                debugLog("New : ", dataValue.toString());
×
UNCOV
697
                debugLog("indexRange=", indexRange);
×
698
            }
×
699

16✔
700
            if (this.oldDataValue !== badDataUnavailable && sameVariant(dataValue.value, this.oldDataValue.value)) {
16!
701
                // c8 ignore next
×
702
                doDebug &&
×
UNCOV
703
                    debugLog("recordValue => Rejected ", this.node?.browseName.toString(), " because useIndexRange && sameVariant");
×
UNCOV
704
                return false;
×
UNCOV
705
            }
×
706
        }
16✔
707

27,021✔
708
        // processTriggerItems
27,021✔
709
        this.triggerLinkedItems();
27,021✔
710

26,863✔
711
        // store last value
26,863✔
712
        this._enqueue_value(dataValue);
26,863✔
713
        // c8 ignore next
26,863✔
714
        doDebug && debugLog("recordValue => OK ", this.node?.browseName.toString());
216,248!
715
        return true;
216,248✔
716
    }
216,248✔
717

2✔
718
    public hasLinkItem(linkedMonitoredItemId: number): boolean {
2✔
719
        if (!this._linkedItems) {
24!
UNCOV
720
            return false;
×
UNCOV
721
        }
×
722
        return this._linkedItems.findIndex((x) => x === linkedMonitoredItemId) > 0;
24✔
723
    }
24✔
724
    public addLinkItem(linkedMonitoredItemId: number): StatusCode {
2✔
725
        if (linkedMonitoredItemId === this.monitoredItemId) {
25✔
726
            return StatusCodes.BadMonitoredItemIdInvalid;
1✔
727
        }
1✔
728
        this._linkedItems = this._linkedItems || [];
25✔
729
        if (this.hasLinkItem(linkedMonitoredItemId)) {
25!
UNCOV
730
            return StatusCodes.BadMonitoredItemIdInvalid; // nothing to do
×
UNCOV
731
        }
×
732
        this._linkedItems.push(linkedMonitoredItemId);
25✔
733
        return StatusCodes.Good;
24✔
734
    }
25✔
735
    public removeLinkItem(linkedMonitoredItemId: number): StatusCode {
2✔
736
        if (!this._linkedItems || linkedMonitoredItemId === this.monitoredItemId) {
9✔
737
            return StatusCodes.BadMonitoredItemIdInvalid;
3✔
738
        }
3✔
739
        const index = this._linkedItems.findIndex((x) => x === linkedMonitoredItemId);
9✔
740
        if (index === -1) {
9✔
741
            return StatusCodes.BadMonitoredItemIdInvalid;
1✔
742
        }
1✔
743
        this._linkedItems.splice(index, 1);
9✔
744
        return StatusCodes.Good;
5✔
745
    }
9✔
746
    /**
2✔
747
     * @private
2✔
748
     */
2✔
749
    private triggerLinkedItems() {
2✔
750
        if (!this.$subscription || !this._linkedItems) {
26,863✔
751
            return;
26,849✔
752
        }
26,849✔
753
        // see https://reference.opcfoundation.org/v104/Core/docs/Part4/5.12.1/#5.12.1.6
189✔
754
        for (const linkItem of this._linkedItems) {
26,863✔
755
            const linkedMonitoredItem = this.$subscription.getMonitoredItem(linkItem);
26✔
756
            if (!linkedMonitoredItem) {
26!
UNCOV
757
                // monitoredItem may have been deleted
×
UNCOV
758
                continue;
×
UNCOV
759
            }
×
760
            if (linkedMonitoredItem.monitoringMode === MonitoringMode.Disabled) {
26✔
761
                continue;
2✔
762
            }
2✔
763
            if (linkedMonitoredItem.monitoringMode === MonitoringMode.Reporting) {
26✔
764
                continue;
2✔
765
            }
2✔
766
            assert(linkedMonitoredItem.monitoringMode === MonitoringMode.Sampling);
22✔
767

22✔
768
            // c8 ignore next
22✔
769
            if (doDebug) {
26!
UNCOV
770
                debugLog("triggerLinkedItems => ", this.node?.nodeId.toString(), linkedMonitoredItem.node?.nodeId.toString());
×
UNCOV
771
            }
×
772
            linkedMonitoredItem.trigger();
26✔
773
        }
22✔
774
    }
189✔
775

2✔
776
    get hasMonitoredItemNotifications(): boolean {
2✔
777
        return this.queue.length > 0 || (this._triggeredNotifications !== undefined && this._triggeredNotifications.length > 0);
43,116!
778
    }
43,116✔
779

2✔
780
    /**
2✔
781
     * @private
2✔
782
     */
2✔
783
    private trigger() {
2✔
784
        setImmediate(() => {
22✔
785
            this._triggeredNotifications = this._triggeredNotifications || [];
22✔
786
            const notifications = this.extractMonitoredItemNotifications(true);
22✔
787
            this._triggeredNotifications = ([] as QueueItem[]).concat(this._triggeredNotifications!, notifications);
22✔
788
        });
22✔
789
    }
22✔
790

2✔
791
    public extractMonitoredItemNotifications(bForce = false): QueueItem[] {
2✔
792
        if (!bForce && this.monitoringMode === MonitoringMode.Sampling && this._triggeredNotifications) {
21,284✔
793
            const notifications1 = this._triggeredNotifications;
22✔
794
            this._triggeredNotifications = undefined;
22✔
795
            return notifications1;
22✔
796
        }
22✔
797
        if (!bForce && this.monitoringMode !== MonitoringMode.Reporting) {
21,284✔
798
            return [];
74✔
799
        }
74✔
800
        const notifications = this.queue;
21,210✔
801
        this._empty_queue();
21,188✔
802

21,188✔
803
        // apply semantic changed bit if necessary
21,188✔
804
        if (notifications.length > 0 && this.node && this._semantic_version < (this.node as UAVariable).semantic_version) {
21,284!
805
            const dataValue = notifications[notifications.length - 1];
×
806
            setSemanticChangeBit(dataValue);
×
UNCOV
807
            assert(this.node.nodeClass === NodeClass.Variable);
×
UNCOV
808
            this._semantic_version = (this.node as UAVariable).semantic_version;
×
UNCOV
809
        }
×
810

21,210✔
811
        return notifications;
21,210✔
812
    }
21,210✔
813

2✔
814
    public modify(
2✔
815
        timestampsToReturn: TimestampsToReturn | null,
6,674✔
816
        monitoringParameters: MonitoringParameters | null
6,674✔
817
    ): MonitoredItemModifyResult {
6,674✔
818
        assert(monitoringParameters instanceof MonitoringParameters);
6,674✔
819

6,674✔
820
        const old_samplingInterval = this.samplingInterval;
6,674✔
821

6,674✔
822
        this.timestampsToReturn = timestampsToReturn || this.timestampsToReturn;
6,674✔
823

6,674✔
824
        if (!monitoringParameters) {
6,674!
825
            return new MonitoredItemModifyResult({
×
826
                revisedQueueSize: this.queueSize,
×
827
                revisedSamplingInterval: this.samplingInterval,
×
828
                filterResult: null,
×
UNCOV
829
                statusCode: StatusCodes.Good
×
UNCOV
830
            });
×
UNCOV
831
        }
×
832
        if (old_samplingInterval !== 0 && monitoringParameters.samplingInterval === 0) {
6,674✔
833
            monitoringParameters.samplingInterval = MonitoredItem.minimumSamplingInterval; // fastest possible
5✔
834
        }
5✔
835

6,674✔
836
        // spec says: Illegal request values for parameters that can be revised do not generate errors. Instead the
6,674✔
837
        // server will choose default values and indicate them in the corresponding revised parameter
6,674✔
838
        this._set_parameters(monitoringParameters);
6,674✔
839

6,674✔
840
        this._adjust_queue_to_match_new_queue_size();
6,674✔
841

6,674✔
842
        this._adjustSampling(old_samplingInterval);
6,674✔
843

6,674✔
844
        if (monitoringParameters.filter) {
6,674✔
845
            const statusCodeFilter = validateFilter(monitoringParameters.filter, this.itemToMonitor, this.node!, {
3✔
846
                maxWhereClauseParameters: this.$subscription?.maxWhereClauseParameters,
3✔
847
                maxSelectClauseParameters: this.$subscription?.maxSelectClauseParameters
3✔
848
            });
3✔
849
            if (statusCodeFilter.isNot(StatusCodes.Good)) {
3✔
850
                return new MonitoredItemModifyResult({
2✔
851
                    statusCode: statusCodeFilter
2✔
852
                });
2✔
853
            }
2✔
854
        }
3✔
855

6,673✔
856
        // validate filter
6,673✔
857
        // note : The DataChangeFilter does not have an associated result structure.
6,673✔
858
        const filterResult = null; // new subscription_service.DataChangeFilter
6,673✔
859

6,672✔
860
        return new MonitoredItemModifyResult({
6,672✔
861
            filterResult,
6,672✔
862
            revisedQueueSize: this.queueSize,
6,672✔
863
            revisedSamplingInterval: this.samplingInterval,
6,672✔
864
            statusCode: StatusCodes.Good
6,672✔
865
        });
6,672✔
866
    }
6,673✔
867

2✔
868
    public async resendInitialValue(): Promise<void> {
2✔
869
        // the first Publish response(s) after the TransferSubscriptions call shall contain the current values of all
12✔
870
        // Monitored Items in the Subscription where the Monitoring Mode is set to Reporting.
12✔
871
        // the first Publish response after the TransferSubscriptions call shall contain only the value changes since
12✔
872
        // the last Publish response was sent.
12✔
873
        // This parameter only applies to MonitoredItems used for monitoring Attribute changes.
12✔
874

12✔
875
        // c8 ignore next
12✔
876
        if (!this.node) return;
12✔
877

12✔
878
        const sessionContext = this.getSessionContext() || SessionContext.defaultContext;
12✔
879

12✔
880
        // c8 ignore next
12✔
881
        if (!sessionContext) return;
12✔
882

12✔
883
        // no need to resend if a value is already in the queue
12✔
884
        if (this.queue.length > 0) return;
12✔
885

10✔
886
        const theValueToResend =
10✔
887
            this.oldDataValue !== badDataUnavailable
10✔
888
                ? this.oldDataValue
12✔
889
                : this.node.readAttribute(sessionContext, this.itemToMonitor.attributeId);
12✔
890
        this.oldDataValue = badDataUnavailable;
12✔
891
        this._enqueue_value(theValueToResend);
12✔
892
    }
12✔
893

2✔
894
    private getSessionContext(): ISessionContext | null {
2✔
895
        const session = this._getSession();
230,841✔
896
        if (!session) {
230,841✔
897
            return null;
8,358✔
898
        }
8,358✔
899
        const sessionContext: ISessionContext = session!.sessionContext;
222,494✔
900
        return sessionContext;
222,483✔
901
    }
222,494✔
902
    /**
2✔
903
     * @private
2✔
904
     */
2✔
905
    private _on_sampling_timer() {
2✔
906
        if (this.monitoringMode === MonitoringMode.Disabled) {
209,471!
UNCOV
907
            return;
×
908
        }
×
909

209,471✔
910
        // Use default context if session is not available
209,471✔
911
        const sessionContext = this.getSessionContext() || SessionContext.defaultContext;
209,471✔
912

209,471✔
913
        if (!sessionContext) {
209,471!
914
            warningLog("MonitoredItem#_on_sampling_timer : ", this.node?.nodeId.toString(), "cannot find session");
×
915
            return;
×
916
        }
×
917
        // c8 ignore next
209,471✔
918
        if (doDebug2) {
209,471!
919
            debugLog(
×
UNCOV
920
                "MonitoredItem#_on_sampling_timer",
×
UNCOV
921
                this.node ? this.node.nodeId.toString() : "null",
×
UNCOV
922
                " isSampling?=",
×
UNCOV
923
                this._is_sampling
×
UNCOV
924
            );
×
UNCOV
925
        }
×
926

209,471✔
927
        if (this._samplingId) {
209,471✔
928
            assert(this.monitoringMode === MonitoringMode.Sampling || this.monitoringMode === MonitoringMode.Reporting);
201,122✔
929

201,122✔
930
            if (this._is_sampling) {
201,122✔
931
                // previous sampling call is not yet completed..
10✔
932
                // there is nothing we can do about it except waiting until next tick.
10✔
933
                // note : see also issue #156 on github
10✔
934

10✔
935
                // Note: some server returns GoodOverload here
10✔
936
                const statusCode = StatusCodes.GoodOverload;
10✔
937

10✔
938
                return;
10✔
939
            }
10✔
940
            assert(!this._is_sampling, "sampling func shall not be re-entrant !! fix it");
201,112✔
941

201,112✔
942
            // c8 ignore next
201,112✔
943
            if (!this.samplingFunc) {
201,122!
UNCOV
944
                throw new Error("internal error : missing samplingFunc");
×
945
            }
×
946

201,122✔
947
            this._is_sampling = true;
201,122✔
948

201,112✔
949
            this.samplingFunc.call(this, sessionContext, this.oldDataValue, (err: Error | null, newDataValue?: DataValue) => {
201,112✔
950
                if (!this._samplingId) {
201,108!
951
                    // item has been disposed. The monitored item has been disposed while the async sampling func
×
UNCOV
952
                    // was taking place ... just ignore this
×
UNCOV
953
                    return;
×
UNCOV
954
                }
×
955
                // c8 ignore next
201,108✔
956
                if (err) {
201,108!
UNCOV
957
                    errorLog(" SAMPLING ERROR =>", err);
×
958
                } else {
201,108✔
959
                    // only record value if source timestamp is newer
201,108✔
960
                    // xx if (newDataValue && isSourceNewerThan(newDataValue, this.oldDataValue)) {
201,108✔
961
                    this._on_value_changed(newDataValue!);
201,108✔
962
                    // xx }
201,108✔
963
                }
201,108✔
964
                this._is_sampling = false;
201,108✔
965
            });
201,112✔
966
        } else {
209,471✔
967
            /* c8 ignore next */
2✔
968
            debugLog("_on_sampling_timer call but MonitoredItem has been terminated !!! ");
2✔
969
        }
8,349✔
970
    }
209,471✔
971

2✔
972
    private _stop_sampling() {
2✔
973
        // debugLog("MonitoredItem#_stop_sampling");
50,916✔
974
        /* c8 ignore next */
2✔
975
        if (!this.node) {
2✔
UNCOV
976
            throw new Error("Internal Error");
×
UNCOV
977
        }
×
978
        if (this._on_opcua_event_received_callback) {
50,916✔
979
            assert(typeof this._on_opcua_event_received_callback === "function");
24✔
980
            (this.node as BaseNode).removeListener("event", this._on_opcua_event_received_callback);
24✔
981
            this._on_opcua_event_received_callback = null;
24✔
982
        }
24✔
983

50,916✔
984
        if (this._attribute_changed_callback) {
50,916✔
985
            assert(typeof this._attribute_changed_callback === "function");
103✔
986

103✔
987
            const event_name = makeAttributeEventName(this.itemToMonitor.attributeId);
103✔
988
            (this.node as BaseNode).removeListener(event_name, this._attribute_changed_callback);
103✔
989
            this._attribute_changed_callback = null;
103✔
990
        }
103✔
991

50,916✔
992
        if (this._value_changed_callback) {
50,916✔
993
            // samplingInterval was 0 for a exception-based data Item
316✔
994
            // we setup a event listener that we need to unwind here
316✔
995
            assert(typeof this._value_changed_callback === "function");
316✔
996
            assert(!this._samplingId);
316✔
997

316✔
998
            (this.node as UAVariable).removeListener("value_changed", this._value_changed_callback);
316✔
999
            this._value_changed_callback = null;
316✔
1000
        }
316✔
1001

50,916✔
1002
        if (this._semantic_changed_callback) {
50,916✔
1003
            assert(typeof this._semantic_changed_callback === "function");
316✔
1004
            assert(!this._samplingId);
316✔
1005
            (this.node as UAVariable).removeListener("semantic_changed", this._semantic_changed_callback);
316✔
1006
            this._semantic_changed_callback = null;
316✔
1007
        }
316✔
1008
        if (this._samplingId) {
50,916✔
1009
            this._clear_timer();
20,913✔
1010
        }
20,913✔
1011

50,916✔
1012
        assert(!this._samplingId);
50,916✔
1013
        assert(!this._value_changed_callback);
50,916✔
1014
        assert(!this._semantic_changed_callback);
50,916✔
1015
        assert(!this._attribute_changed_callback);
50,916✔
1016
        assert(!this._on_opcua_event_received_callback);
50,916✔
1017
    }
50,916✔
1018

2✔
1019
    private _on_value_changed(dataValue: DataValue, indexRange?: NumericRange) {
2✔
1020
        assert(dataValue instanceof DataValue);
201,523✔
1021
        this.recordValue(dataValue, false, indexRange);
201,523✔
1022
    }
201,523✔
1023

2✔
1024
    private _on_semantic_changed() {
2✔
1025
        const dataValue: DataValue = (this.node! as UAVariable).readValue();
2✔
1026
        this._on_value_changed(dataValue);
2✔
1027
    }
2✔
1028

2✔
1029
    private _on_opcua_event(eventData: IEventData) {
2✔
1030
        // TO DO : => Improve Filtering, bearing in mind that ....
117✔
1031
        // Release 1.04 8 OPC Unified Architecture, Part 9
117✔
1032
        // 4.5 Condition state synchronization
117✔
1033
        // To ensure a Client is always informed, the three special EventTypes
117✔
1034
        // (RefreshEndEventType, RefreshStartEventType and RefreshRequiredEventType)
117✔
1035
        // ignore the Event content filtering associated with a Subscription and will always be
117✔
1036
        // delivered to the Client.
117✔
1037

117✔
1038
        // c8 ignore next
117✔
1039
        if (!this.filter || !(this.filter instanceof EventFilter)) {
117✔
UNCOV
1040
            throw new Error("Internal Error : a EventFilter is requested");
×
UNCOV
1041
        }
×
1042

117✔
1043
        const addressSpace: AddressSpace = eventData.getEventDataSource().addressSpace as AddressSpace;
117✔
1044

117✔
1045
        if (!checkWhereClauseOnAddressSpace(addressSpace, SessionContext.defaultContext, this.filter.whereClause, eventData)) {
117✔
1046
            return;
22✔
1047
        }
22✔
1048

95✔
1049
        const selectClauses = this.filter.selectClauses ? this.filter.selectClauses : ([] as SimpleAttributeOperand[]);
117✔
1050

117✔
1051
        const eventFields: Variant[] = extractEventFields(SessionContext.defaultContext, selectClauses, eventData);
117✔
1052

117✔
1053
        // c8 ignore next
117✔
1054
        if (doDebug) {
117✔
UNCOV
1055
            debugLog(" RECEIVED INTERNAL EVENT THAT WE ARE MONITORING");
×
UNCOV
1056
            debugLog(this.filter ? this.filter.toString() : "no filter");
×
UNCOV
1057
            eventFields.forEach((e: any) => {
×
UNCOV
1058
                debugLog(e.toString());
×
UNCOV
1059
            });
×
UNCOV
1060
        }
×
1061

95✔
1062
        this._enqueue_event(eventFields);
95✔
1063
    }
95✔
1064

2✔
1065
    private _getSession(): null | IServerSession2 {
2✔
1066
        if (!this.$subscription) {
230,841✔
1067
            return null;
8,348✔
1068
        }
8,348✔
1069
        if (!this.$subscription.$session) {
230,841✔
1070
            return null;
10✔
1071
        }
10✔
1072
        return this.$subscription.$session;
222,494✔
1073
    }
222,494✔
1074

2✔
1075
    private _start_sampling(recordInitialValue: boolean): void {
2✔
1076
        // c8 ignore next
21,416✔
1077
        if (!this.node) {
21,416!
UNCOV
1078
            return; // we just want to ignore here ...
×
UNCOV
1079
        }
×
1080
        this.oldDataValue = badDataUnavailable;
21,416✔
1081
        setImmediate(() => this.__start_sampling(recordInitialValue));
21,416✔
1082
    }
21,416✔
1083

2✔
1084
    private __acquireInitialValue(sessionContext: ISessionContext, callback: CallbackT<DataValue>): void {
2✔
1085
        // acquire initial value from the variable/object not itself or from the last known value if we have
14,569✔
1086
        // one already
14,569✔
1087
        assert(this.itemToMonitor.attributeId === AttributeIds.Value);
14,569✔
1088
        assert(this.node);
14,569✔
1089
        if (this.node?.nodeClass !== NodeClass.Variable) {
14,569!
UNCOV
1090
            return callback(new Error("Invalid "));
×
UNCOV
1091
        }
×
1092
        const variable = this.node as UAVariable;
14,569✔
1093
        if (this.oldDataValue == badDataUnavailable) {
14,569✔
1094
            variable.readValueAsync(sessionContext, (err: Error | null, dataValue?: DataValue) => {
14,569✔
1095
                callback(err, dataValue);
14,569✔
1096
            });
14,569✔
1097
        } else {
14,569!
1098
            const o = this.oldDataValue;
×
1099
            this.oldDataValue = badDataUnavailable;
×
UNCOV
1100
            // c8 ignore next
×
UNCOV
1101
            if (doDebug) {
×
UNCOV
1102
                safeGuardRegister(this);
×
UNCOV
1103
            }
×
UNCOV
1104
            callback(null, o);
×
UNCOV
1105
        }
×
1106
    }
14,569✔
1107
    private __start_sampling(recordInitialValue?: boolean): void {
2✔
1108
        // c8 ignore next
21,387✔
1109
        if (!this.node) {
21,387✔
1110
            return; // we just want to ignore here ...
29✔
1111
        }
29✔
1112

21,387✔
1113
        const sessionContext = this.getSessionContext() || SessionContext.defaultContext;
21,387✔
1114
        // c8 ignore next
21,387✔
1115
        if (!sessionContext) {
21,387!
UNCOV
1116
            return;
×
UNCOV
1117
        }
×
1118

21,387✔
1119
        this._stop_sampling();
21,387✔
1120

21,358✔
1121
        if (this.itemToMonitor.attributeId === AttributeIds.EventNotifier) {
21,387✔
1122
            // c8 ignore next
24✔
1123
            if (doDebug) {
24!
UNCOV
1124
                debugLog("xxxxxx monitoring EventNotifier on", this.node.nodeId.toString(), this.node.browseName.toString());
×
UNCOV
1125
            }
×
1126
            if (!this._on_opcua_event_received_callback) {
24✔
1127
                // we are monitoring OPCUA Event
24✔
1128
                this._on_opcua_event_received_callback = this._on_opcua_event.bind(this);
24✔
1129
                if (this.node && this.node.nodeClass == NodeClass.Object) {
24✔
1130
                    this.node.on("event", this._on_opcua_event_received_callback);
24✔
1131
                }
24✔
1132
            }
24✔
1133
            return;
24✔
1134
        }
24✔
1135
        if (this.itemToMonitor.attributeId !== AttributeIds.Value) {
21,387✔
1136
            // sampling interval only applies to Value Attributes.
103✔
1137
            this.samplingInterval = 0; // turned to exception-based regardless of requested sampling interval
103✔
1138

103✔
1139
            // non value attribute only react on value change
103✔
1140
            if (!this._attribute_changed_callback) {
103✔
1141
                this._attribute_changed_callback = this._on_value_changed.bind(this);
103✔
1142
                const event_name = makeAttributeEventName(this.itemToMonitor.attributeId);
103✔
1143
                (this.node as BaseNode).on(event_name, this._attribute_changed_callback);
103✔
1144
            }
103✔
1145

103✔
1146
            if (recordInitialValue) {
103✔
1147
                // read initial value
100✔
1148
                const dataValue = this.node.readAttribute(sessionContext, this.itemToMonitor.attributeId);
100✔
1149
                this.recordValue(dataValue, true);
100✔
1150
            }
100✔
1151
            return;
103✔
1152
        }
103✔
1153

21,260✔
1154
        if (this.samplingInterval === 0) {
21,387✔
1155
            // we have a exception-based dataItem : event based model, so we do not need a timer
316✔
1156
            // rather , we setup the "value_changed_event";
316✔
1157
            if (!this._value_changed_callback) {
316✔
1158
                assert(!this._semantic_changed_callback);
316✔
1159
                this._value_changed_callback = this._on_value_changed.bind(this);
316✔
1160
                this._semantic_changed_callback = this._on_semantic_changed.bind(this);
316✔
1161
                if (this.node.nodeClass == NodeClass.Variable) {
316✔
1162
                    this.node.on("value_changed", this._value_changed_callback);
316✔
1163
                    this.node.on("semantic_changed", this._semantic_changed_callback);
316✔
1164
                }
316✔
1165
            }
316✔
1166

316✔
1167
            // initiate first read
316✔
1168
            if (recordInitialValue) {
316✔
1169
                this.__acquireInitialValue(sessionContext, (err: Error | null, dataValue?: DataValue) => {
316✔
1170
                    if (err) {
316!
UNCOV
1171
                        warningLog(err.message);
×
UNCOV
1172
                    }
×
1173
                    if (!err && dataValue) {
316✔
1174
                        this.recordValue(dataValue.clone(), true);
316✔
1175
                    }
316✔
1176
                });
316✔
1177
            }
316✔
1178
        } else {
21,387✔
1179
            if (recordInitialValue) {
20,915✔
1180
                this.__acquireInitialValue(sessionContext, (err: Error | null, dataValue?: DataValue) => {
14,253✔
1181
                    if (err) {
14,253!
UNCOV
1182
                        warningLog(err.message);
×
UNCOV
1183
                    }
×
1184
                    if (!err && dataValue) {
14,253✔
1185
                        this.recordValue(dataValue, true);
14,253✔
1186
                    }
14,253✔
1187
                    this._set_timer();
14,252✔
1188
                });
14,253✔
1189
            } else {
20,915✔
1190
                this._set_timer();
6,662✔
1191
            }
6,662✔
1192
        }
20,915✔
1193
    }
21,387✔
1194

2✔
1195
    private _set_parameters(monitoredParameters: MonitoringParameters) {
2✔
1196
        _validate_parameters(monitoredParameters);
21,445✔
1197
        // only change clientHandle if it is valid (0<X<MAX)
21,445✔
1198
        if (monitoredParameters.clientHandle !== 0 && monitoredParameters.clientHandle !== 4294967295) {
21,445✔
1199
            this.clientHandle = monitoredParameters.clientHandle;
14,717✔
1200
        }
14,717✔
1201

21,445✔
1202
        // The Server may support data that is collected based on a sampling model or generated based on an
21,445✔
1203
        // exception-based model. The fastest supported sampling interval may be equal to 0, which indicates
21,445✔
1204
        // that the data item is exception-based rather than being sampled at some period. An exception-based
21,445✔
1205
        // model means that the underlying system does not require sampling and reports data changes.
21,445✔
1206
        if (this.node && this.node.nodeClass === NodeClass.Variable) {
21,445✔
1207
            const variable = this.node as UAVariable;
6,672✔
1208
            this.samplingInterval = _adjust_sampling_interval(
6,672✔
1209
                monitoredParameters.samplingInterval,
6,672✔
1210
                variable.minimumSamplingInterval || 0
6,672✔
1211
            );
6,672✔
1212
        } else {
21,445✔
1213
            this.samplingInterval = _adjust_sampling_interval(monitoredParameters.samplingInterval, 0);
14,773✔
1214
        }
14,773✔
1215
        this.discardOldest = monitoredParameters.discardOldest;
21,445✔
1216
        this.queueSize = _adjust_queue_size(monitoredParameters.queueSize);
21,445✔
1217

21,445✔
1218
        // change filter
21,445✔
1219
        this.filter = (monitoredParameters.filter as MonitoringFilter) || null;
21,445✔
1220
    }
21,445✔
1221

2✔
1222
    private _setOverflowBit(notification: any) {
2✔
1223
        if (Object.hasOwn(notification, "value")) {
73✔
1224
            assert(notification.value.statusCode.equals(StatusCodes.Good));
73✔
1225
            notification.value.statusCode = StatusCode.makeStatusCode(
73✔
1226
                notification.value.statusCode,
73✔
1227
                "Overflow | InfoTypeDataValue"
73✔
1228
            );
73✔
1229
            assert(sameStatusCode(notification.value.statusCode, StatusCodes.GoodWithOverflowBit));
73✔
1230
            assert(notification.value.statusCode.hasOverflowBit);
73✔
1231
        }
73✔
1232
        if (this.$subscription && this.$subscription.subscriptionDiagnostics) {
73✔
1233
            this.$subscription.subscriptionDiagnostics.monitoringQueueOverflowCount++;
64✔
1234
        }
64✔
1235
        // to do: eventQueueOverflowCount
73✔
1236
    }
73✔
1237

2✔
1238
    private _enqueue_notification(notification: QueueItem) {
2✔
1239
        if (this.queueSize === 1) {
27,012✔
1240
            // https://reference.opcfoundation.org/v104/Core/docs/Part4/5.12.1/#5.12.1.5
13,943✔
1241
            // If the queue size is one, the queue becomes a buffer that always contains the newest
13,943✔
1242
            // Notification. In this case, if the sampling interval of the MonitoredItem is faster
13,943✔
1243
            // than the publishing interval of the Subscription, the MonitoredItem will be over
13,943✔
1244
            // sampling and the Client will always receive the most up-to-date value.
13,943✔
1245
            // The discard policy is ignored if the queue size is one.
13,943✔
1246
            // ensure queue size
13,943✔
1247
            if (!this.queue || this.queue.length !== 1) {
13,943✔
1248
                this.queue = [];
13,858✔
1249
            }
13,858✔
1250
            this.queue[0] = notification;
13,943✔
1251
            assert(this.queue.length === 1);
13,943✔
1252
        } else {
27,012✔
1253
            if (this.discardOldest) {
13,069✔
1254
                // push new value to queue
2,957✔
1255
                this.queue.push(notification);
2,957✔
1256

2,957✔
1257
                if (this.queue.length > this.queueSize) {
2,957✔
1258
                    this.overflow = true;
64✔
1259

64✔
1260
                    this.queue.shift(); // remove front element
64✔
1261

64✔
1262
                    // set overflow bit
64✔
1263
                    this._setOverflowBit(this.queue[0]);
64✔
1264
                }
64✔
1265
            } else {
13,069✔
1266
                if (this.queue.length < this.queueSize) {
10,112✔
1267
                    this.queue.push(notification);
10,103✔
1268
                } else {
10,112✔
1269
                    this.overflow = true;
9✔
1270

9✔
1271
                    this._setOverflowBit(notification);
9✔
1272
                    this.queue[this.queue.length - 1] = notification;
9✔
1273
                }
9✔
1274
            }
10,112✔
1275
        }
13,069✔
1276
        assert(this.queue.length >= 1);
27,012✔
1277
    }
27,012✔
1278

2✔
1279
    private _makeDataChangeNotification(dataValue: DataValue): MonitoredItemNotification {
2✔
1280
        if (this.clientHandle === -1 || this.clientHandle === 4294967295) {
26,917!
UNCOV
1281
            debugLog("Invalid client handle");
×
UNCOV
1282
        }
×
1283
        const attributeId = this.itemToMonitor.attributeId;
26,917✔
1284
        // if dataFilter is specified ....
26,917✔
1285
        if (this.filter && this.filter instanceof DataChangeFilter) {
26,917✔
1286
            if (this.filter.trigger === DataChangeTrigger.Status) {
111✔
1287
                /** */
5✔
1288
            }
5✔
1289
        }
111✔
1290
        dataValue = apply_timestamps(dataValue, this.timestampsToReturn, attributeId);
26,917✔
1291
        return new MonitoredItemNotification({
26,917✔
1292
            clientHandle: this.clientHandle,
26,917✔
1293
            value: dataValue
26,917✔
1294
        });
26,917✔
1295
    }
26,917✔
1296

2✔
1297
    /**
2✔
1298
     * @param dataValue {DataValue} the dataValue to enqueue
2✔
1299
     * @private
2✔
1300
     */
2✔
1301
    public _enqueue_value(dataValue: DataValue) {
2✔
1302
        // preconditions:
26,917✔
1303
        doDebug && debugLog("_enqueue_value = ", dataValue.toString());
26,917!
1304

26,917✔
1305
        // lets verify that, if status code is good then we have a valid Variant in the dataValue
26,917✔
1306
        doDebug && assert(!dataValue.statusCode.isGoodish() || dataValue.value instanceof Variant);
26,917!
1307
        // let's check that data Value is really a different object
26,917✔
1308
        // we may end up with corrupted queue if dataValue are recycled and stored as is in notifications
26,917✔
1309
        doDebug && assert(dataValue !== this.oldDataValue, "dataValue cannot be the same object twice!");
26,917!
1310

26,917✔
1311
        // let's check that data Value is really a different object
26,917✔
1312
        // we may end up with corrupted queue if dataValue are recycled and stored as is in notifications
26,917✔
1313
        if (
26,917✔
1314
            !(
26,917✔
1315
                !this.oldDataValue.value ||
26,917✔
1316
                !dataValue.value ||
26,917✔
1317
                !(dataValue.value.value instanceof Object) ||
26,917✔
1318
                dataValue.value.value !== this.oldDataValue.value.value
10,992✔
1319
            ) &&
26,697!
1320
            !(dataValue.value.value instanceof Date)
×
1321
        ) {
26,917!
1322
            throw new Error(
×
1323
                "dataValue.value.value cannot be the same object twice! " +
×
1324
                    this.node!.browseName.toString() +
×
UNCOV
1325
                    " " +
×
UNCOV
1326
                    dataValue.toString() +
×
UNCOV
1327
                    "  " +
×
1328
                    chalk.cyan(this.oldDataValue.toString())
×
1329
            );
×
1330
        }
×
1331

26,917✔
1332
        // c8 ignore next
26,917✔
1333
        if (doDebug) {
26,917!
1334
            debugLog("MonitoredItem#_enqueue_value", this.node!.nodeId.toString());
×
1335
            safeGuardVerify(this);
×
UNCOV
1336
        }
×
1337
        this.oldDataValue = dataValue.clone();
26,917✔
1338
        // c8 ignore next
26,917✔
1339
        if (doDebug) {
26,917!
UNCOV
1340
            safeGuardRegister(this);
×
UNCOV
1341
        }
×
1342

26,917✔
1343
        const notification = this._makeDataChangeNotification(this.oldDataValue);
26,917✔
1344
        this._enqueue_notification(notification);
26,917✔
1345
    }
26,917✔
1346

2✔
1347
    private _makeEventFieldList(eventFields: any[]): EventFieldList {
2✔
1348
        return new EventFieldList({
95✔
1349
            clientHandle: this.clientHandle,
95✔
1350
            eventFields
95✔
1351
        });
95✔
1352
    }
95✔
1353

2✔
1354
    private _enqueue_event(eventFields: any[]) {
2✔
1355
        // c8 ignore next
95✔
1356
        if (doDebug) {
95✔
UNCOV
1357
            debugLog(" MonitoredItem#_enqueue_event");
×
UNCOV
1358
        }
×
1359
        const notification = this._makeEventFieldList(eventFields);
95✔
1360
        this._enqueue_notification(notification);
95✔
1361
    }
95✔
1362

2✔
1363
    private _empty_queue() {
2✔
1364
        // empty queue
21,203✔
1365
        this.queue = [];
21,203✔
1366
        this.overflow = false;
21,203✔
1367
    }
21,203✔
1368

2✔
1369
    private _clear_timer() {
2✔
1370
        if (this._samplingId) {
20,913✔
1371
            if (useCommonTimer) {
20,913✔
1372
                removeFromTimer(this);
20,913✔
1373
            } else {
20,913!
UNCOV
1374
                clearInterval(this._samplingId);
×
UNCOV
1375
            }
×
1376
            this._samplingId = undefined;
20,913✔
1377
        }
20,913✔
1378
    }
20,913✔
1379

2✔
1380
    private _set_timer() {
2✔
1381
        if (!this.itemToMonitor) {
20,914✔
1382
            // item has already been deleted
1✔
1383
            // so do not create the timer !
1✔
1384
            return;
1✔
1385
        }
1✔
1386

20,913✔
1387
        assert(this.samplingInterval >= MonitoredItem.minimumSamplingInterval);
20,913✔
1388
        assert(!this._samplingId);
20,913✔
1389

20,913✔
1390
        if (useCommonTimer) {
20,913✔
1391
            this._samplingId = appendToTimer(this);
20,913✔
1392
        } else {
20,914!
UNCOV
1393
            // settle periodic sampling
×
UNCOV
1394
            this._samplingId = setInterval(() => {
×
UNCOV
1395
                this._on_sampling_timer();
×
UNCOV
1396
            }, this.samplingInterval);
×
UNCOV
1397
        }
×
1398
    }
20,914✔
1399

2✔
1400
    private _adjust_queue_to_match_new_queue_size() {
2✔
1401
        // adjust queue size if necessary
6,674✔
1402
        if (this.queueSize < this.queue.length) {
6,674✔
1403
            if (this.discardOldest) {
3✔
1404
                this.queue.splice(0, this.queue.length - this.queueSize);
1✔
1405
            } else {
3✔
1406
                const lastElement = this.queue[this.queue.length - 1];
2✔
1407
                // only keep queueSize first element, discard others
2✔
1408
                this.queue.splice(this.queueSize);
2✔
1409
                this.queue[this.queue.length - 1] = lastElement;
2✔
1410
            }
2✔
1411
        }
3✔
1412
        if (this.queueSize <= 1) {
6,674✔
1413
            this.overflow = false;
6,667✔
1414
            // unset OverFlowBit
6,667✔
1415
            if (this.queue.length === 1) {
6,667✔
1416
                if (this.queue[0] instanceof MonitoredItemNotification) {
3✔
1417
                    const el = this.queue[0] as MonitoredItemNotification;
3✔
1418
                    if (el.value.statusCode.hasOverflowBit) {
3✔
1419
                        (el.value.statusCode as any).unset("Overflow | InfoTypeDataValue");
1✔
1420
                    }
1✔
1421
                }
3✔
1422
            }
3✔
1423
        }
6,667✔
1424
        assert(this.queue.length <= this.queueSize);
6,674✔
1425
    }
6,674✔
1426

2✔
1427
    private _adjustSampling(old_samplingInterval: number) {
2✔
1428
        if (old_samplingInterval !== this.samplingInterval) {
6,674✔
1429
            this._start_sampling(false);
6,661✔
1430
        }
6,661✔
1431
    }
6,674✔
1432

2✔
1433
    private _on_node_disposed(node: BaseNode) {
2✔
1434
        this._on_value_changed(
1✔
1435
            new DataValue({
1✔
1436
                sourceTimestamp: new Date(),
1✔
1437
                statusCode: StatusCodes.BadNodeIdInvalid
1✔
1438
            })
1✔
1439
        );
1✔
1440
        this._stop_sampling();
1✔
1441
        if (this._on_node_disposed_listener) {
1✔
1442
            node.removeListener("dispose", this._on_node_disposed_listener);
1✔
1443
            this._on_node_disposed_listener = null;
1✔
1444
        }
1✔
1445
    }
1✔
1446
}
2!
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc