node-opcua-server 2.105.1 → 2.107.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/addressSpace_accessor.d.ts +29 -0
- package/dist/addressSpace_accessor.js +346 -0
- package/dist/addressSpace_accessor.js.map +1 -0
- package/dist/base_server.js +1 -1
- package/dist/base_server.js.map +1 -1
- package/dist/i_address_space_accessor.d.ts +11 -0
- package/dist/i_address_space_accessor.js +3 -0
- package/dist/i_address_space_accessor.js.map +1 -0
- package/dist/monitored_item.js +1 -1
- package/dist/monitored_item.js.map +1 -1
- package/dist/opcua_server.d.ts +1 -1
- package/dist/opcua_server.js +46 -46
- package/dist/opcua_server.js.map +1 -1
- package/dist/register_server_manager.js +1 -1
- package/dist/register_server_manager.js.map +1 -1
- package/dist/server_engine.d.ts +15 -103
- package/dist/server_engine.js +36 -370
- package/dist/server_engine.js.map +1 -1
- package/dist/server_publish_engine.js +1 -1
- package/dist/server_publish_engine.js.map +1 -1
- package/dist/server_session.js +1 -1
- package/dist/server_session.js.map +1 -1
- package/dist/server_subscription.d.ts +1 -1
- package/dist/server_subscription.js +7 -5
- package/dist/server_subscription.js.map +1 -1
- package/package.json +26 -26
- package/source/addressSpace_accessor.ts +393 -0
- package/source/i_address_space_accessor.ts +12 -0
- package/source/monitored_item.ts +2 -2
- package/source/opcua_server.ts +70 -83
- package/source/register_server_manager.ts +0 -1
- package/source/server_engine.ts +40 -512
- package/source/server_subscription.ts +2 -2
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { AddressSpace, ensureDatatypeExtracted, SessionContext, resolveOpaqueOnAddressSpace, callMethodHelper } from "node-opcua-address-space";
|
|
3
|
+
import { ISessionContext, ContinuationData, BaseNode, UAVariable } from "node-opcua-address-space-base";
|
|
4
|
+
import assert from "node-opcua-assert";
|
|
5
|
+
import { AttributeIds } from "node-opcua-basic-types";
|
|
6
|
+
import { TimestampsToReturn, DataValue, coerceTimestampsToReturn, apply_timestamps_no_copy } from "node-opcua-data-value";
|
|
7
|
+
import { getCurrentClock, minOPCUADate } from "node-opcua-date-time";
|
|
8
|
+
import { resolveNodeId, coerceNodeId, NodeId } from "node-opcua-nodeid";
|
|
9
|
+
import { NumericRange } from "node-opcua-numeric-range";
|
|
10
|
+
import { doDebug, debugLog } from "node-opcua-pki";
|
|
11
|
+
import { StatusCode, StatusCodes } from "node-opcua-status-code";
|
|
12
|
+
import { BrowseDescriptionOptions, BrowseResult, ReadValueIdOptions, WriteValue, CallMethodRequest, CallMethodResultOptions, HistoryReadValueId, HistoryReadDetails, HistoryReadResult, ReadRequestOptions, HistoryReadRequest, ReadProcessedDetails, BrowseDescription, AggregateConfiguration } from "node-opcua-types";
|
|
13
|
+
import { Variant } from "node-opcua-variant";
|
|
14
|
+
import { IAddressSpaceAccessor } from "./i_address_space_accessor";
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
function checkReadProcessedDetails(historyReadDetails: ReadProcessedDetails): StatusCode {
|
|
19
|
+
if (!historyReadDetails.aggregateConfiguration) {
|
|
20
|
+
historyReadDetails.aggregateConfiguration = new AggregateConfiguration({
|
|
21
|
+
useServerCapabilitiesDefaults: true
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
if (historyReadDetails.aggregateConfiguration.useServerCapabilitiesDefaults) {
|
|
25
|
+
return StatusCodes.Good;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// The PercentDataGood and PercentDataBad shall follow the following relationship
|
|
29
|
+
// PercentDataGood ≥ (100 – PercentDataBad).
|
|
30
|
+
// If they are equal the result of the PercentDataGood calculation is used.
|
|
31
|
+
// If the values entered for PercentDataGood and PercentDataBad do not result in a valid calculation
|
|
32
|
+
// (e.g. Bad = 80; Good = 0) the result will have a StatusCode of Bad_AggregateInvalidInputs.
|
|
33
|
+
if (
|
|
34
|
+
historyReadDetails.aggregateConfiguration.percentDataGood <
|
|
35
|
+
100 - historyReadDetails.aggregateConfiguration.percentDataBad
|
|
36
|
+
) {
|
|
37
|
+
return StatusCodes.BadAggregateInvalidInputs;
|
|
38
|
+
}
|
|
39
|
+
// The StatusCode Bad_AggregateInvalidInputs will be returned if the value of PercentDataGood
|
|
40
|
+
// or PercentDataBad exceed 100.
|
|
41
|
+
if (
|
|
42
|
+
historyReadDetails.aggregateConfiguration.percentDataGood > 100 ||
|
|
43
|
+
historyReadDetails.aggregateConfiguration.percentDataGood < 0
|
|
44
|
+
) {
|
|
45
|
+
return StatusCodes.BadAggregateInvalidInputs;
|
|
46
|
+
}
|
|
47
|
+
if (
|
|
48
|
+
historyReadDetails.aggregateConfiguration.percentDataBad > 100 ||
|
|
49
|
+
historyReadDetails.aggregateConfiguration.percentDataBad < 0
|
|
50
|
+
) {
|
|
51
|
+
return StatusCodes.BadAggregateInvalidInputs;
|
|
52
|
+
}
|
|
53
|
+
return StatusCodes.Good;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface IAddressSpaceAccessorSingle {
|
|
57
|
+
browseNode(browseDescription: BrowseDescriptionOptions, context?: ISessionContext): Promise<BrowseResult>;
|
|
58
|
+
readNode(
|
|
59
|
+
context: ISessionContext,
|
|
60
|
+
nodeToRead: ReadValueIdOptions,
|
|
61
|
+
maxAge: number,
|
|
62
|
+
timestampsToReturn?: TimestampsToReturn
|
|
63
|
+
): Promise<DataValue>;
|
|
64
|
+
writeNode(context: ISessionContext, writeValue: WriteValue): Promise<StatusCode>;
|
|
65
|
+
callMethod(context: ISessionContext, methodToCall: CallMethodRequest): Promise<CallMethodResultOptions>;
|
|
66
|
+
historyReadNode(
|
|
67
|
+
context: ISessionContext,
|
|
68
|
+
nodeToRead: HistoryReadValueId,
|
|
69
|
+
historyReadDetails: HistoryReadDetails,
|
|
70
|
+
timestampsToReturn: TimestampsToReturn,
|
|
71
|
+
continuationData: ContinuationData
|
|
72
|
+
): Promise<HistoryReadResult>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export class AddressSpaceAccessor implements IAddressSpaceAccessor, IAddressSpaceAccessorSingle {
|
|
76
|
+
constructor(public addressSpace: AddressSpace) {}
|
|
77
|
+
|
|
78
|
+
public async browse(context: ISessionContext, nodesToBrowse: BrowseDescriptionOptions[]): Promise<BrowseResult[]> {
|
|
79
|
+
const results: BrowseResult[] = [];
|
|
80
|
+
for (const browseDescription of nodesToBrowse) {
|
|
81
|
+
results.push(await this.browseNode(browseDescription, context));
|
|
82
|
+
assert(browseDescription.nodeId!, "expecting a nodeId");
|
|
83
|
+
}
|
|
84
|
+
return results;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public async read(context: ISessionContext, readRequest: ReadRequestOptions): Promise<DataValue[]> {
|
|
88
|
+
/**
|
|
89
|
+
*
|
|
90
|
+
*
|
|
91
|
+
* @param {number} maxAge: Maximum age of the value to be read in milliseconds.
|
|
92
|
+
*
|
|
93
|
+
* The age of the value is based on the difference between
|
|
94
|
+
* the ServerTimestamp and the time when the Server starts processing the request. For example if the Client
|
|
95
|
+
* specifies a maxAge of 500 milliseconds and it takes 100 milliseconds until the Server starts processing
|
|
96
|
+
* the request, the age of the returned value could be 600 milliseconds prior to the time it was requested.
|
|
97
|
+
* If the Server has one or more values of an Attribute that are within the maximum age, it can return any one
|
|
98
|
+
* of the values or it can read a new value from the data source. The number of values of an Attribute that
|
|
99
|
+
* a Server has depends on the number of MonitoredItems that are defined for the Attribute. In any case,
|
|
100
|
+
* the Client can make no assumption about which copy of the data will be returned.
|
|
101
|
+
* If the Server does not have a value that is within the maximum age, it shall attempt to read a new value
|
|
102
|
+
* from the data source.
|
|
103
|
+
* If the Server cannot meet the requested maxAge, it returns its 'best effort' value rather than rejecting the
|
|
104
|
+
* request.
|
|
105
|
+
* This may occur when the time it takes the Server to process and return the new data value after it has been
|
|
106
|
+
* accessed is greater than the specified maximum age.
|
|
107
|
+
* If maxAge is set to 0, the Server shall attempt to read a new value from the data source.
|
|
108
|
+
* If maxAge is set to the max Int32 value or greater, the Server shall attempt to get a cached value.
|
|
109
|
+
* Negative values are invalid for maxAge.
|
|
110
|
+
*/
|
|
111
|
+
|
|
112
|
+
readRequest.maxAge = readRequest.maxAge || 0;
|
|
113
|
+
const timestampsToReturn = readRequest.timestampsToReturn;
|
|
114
|
+
const nodesToRead = readRequest.nodesToRead || [];
|
|
115
|
+
|
|
116
|
+
context.currentTime = getCurrentClock();
|
|
117
|
+
const dataValues: DataValue[] = [];
|
|
118
|
+
for (const readValueId of nodesToRead) {
|
|
119
|
+
const dataValue = await this.readNode(context, readValueId, readRequest.maxAge, timestampsToReturn);
|
|
120
|
+
dataValues.push(dataValue);
|
|
121
|
+
}
|
|
122
|
+
return dataValues;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public async write(context: ISessionContext, nodesToWrite: WriteValue[]): Promise<StatusCode[]> {
|
|
126
|
+
context.currentTime = getCurrentClock();
|
|
127
|
+
await ensureDatatypeExtracted(this.addressSpace!);
|
|
128
|
+
const results: StatusCode[] = [];
|
|
129
|
+
for (const writeValue of nodesToWrite) {
|
|
130
|
+
const statusCode = await this.writeNode(context, writeValue);
|
|
131
|
+
results.push(statusCode);
|
|
132
|
+
}
|
|
133
|
+
return results;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
public async call(context: ISessionContext, methodsToCall: CallMethodRequest[]): Promise<CallMethodResultOptions[]> {
|
|
137
|
+
const results: CallMethodResultOptions[] = [];
|
|
138
|
+
await ensureDatatypeExtracted(this.addressSpace!);
|
|
139
|
+
for (const methodToCall of methodsToCall) {
|
|
140
|
+
const result = await this.callMethod(context, methodToCall);
|
|
141
|
+
results.push(result);
|
|
142
|
+
}
|
|
143
|
+
return results;
|
|
144
|
+
}
|
|
145
|
+
public async historyRead(context: ISessionContext, historyReadRequest: HistoryReadRequest): Promise<HistoryReadResult[]> {
|
|
146
|
+
assert(context instanceof SessionContext);
|
|
147
|
+
assert(historyReadRequest instanceof HistoryReadRequest);
|
|
148
|
+
|
|
149
|
+
const timestampsToReturn = historyReadRequest.timestampsToReturn;
|
|
150
|
+
const historyReadDetails = historyReadRequest.historyReadDetails! as HistoryReadDetails;
|
|
151
|
+
const releaseContinuationPoints = historyReadRequest.releaseContinuationPoints;
|
|
152
|
+
assert(historyReadDetails instanceof HistoryReadDetails);
|
|
153
|
+
// ReadAnnotationDataDetails | ReadAtTimeDetails | ReadEventDetails | ReadProcessedDetails | ReadRawModifiedDetails;
|
|
154
|
+
|
|
155
|
+
const nodesToRead = historyReadRequest.nodesToRead || ([] as HistoryReadValueId[]);
|
|
156
|
+
assert(Array.isArray(nodesToRead));
|
|
157
|
+
|
|
158
|
+
// special cases with ReadProcessedDetails
|
|
159
|
+
interface M {
|
|
160
|
+
nodeToRead: HistoryReadValueId;
|
|
161
|
+
processDetail: ReadProcessedDetails;
|
|
162
|
+
index: number;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const _q = async (m: M): Promise<HistoryReadResult> => {
|
|
166
|
+
const continuationPoint = m.nodeToRead.continuationPoint;
|
|
167
|
+
return await this.historyReadNode(context, m.nodeToRead, m.processDetail, timestampsToReturn, {
|
|
168
|
+
continuationPoint,
|
|
169
|
+
releaseContinuationPoints /**, index = ??? */
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
if (historyReadDetails instanceof ReadProcessedDetails) {
|
|
174
|
+
//
|
|
175
|
+
if (!historyReadDetails.aggregateType || historyReadDetails.aggregateType.length !== nodesToRead.length) {
|
|
176
|
+
return [new HistoryReadResult({ statusCode: StatusCodes.BadInvalidArgument })];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const parameterStatus = checkReadProcessedDetails(historyReadDetails);
|
|
180
|
+
if (parameterStatus !== StatusCodes.Good) {
|
|
181
|
+
return [new HistoryReadResult({ statusCode: parameterStatus })];
|
|
182
|
+
}
|
|
183
|
+
const promises: Promise<HistoryReadResult>[] = [];
|
|
184
|
+
let index = 0;
|
|
185
|
+
for (const nodeToRead of nodesToRead) {
|
|
186
|
+
const aggregateType = historyReadDetails.aggregateType[index];
|
|
187
|
+
const processDetail = new ReadProcessedDetails({ ...historyReadDetails, aggregateType: [aggregateType] });
|
|
188
|
+
promises.push(_q({ nodeToRead, processDetail, index }));
|
|
189
|
+
index++;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const results: HistoryReadResult[] = await Promise.all(promises);
|
|
193
|
+
return results;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const _r = async (nodeToRead: HistoryReadValueId, index: number) => {
|
|
197
|
+
const continuationPoint = nodeToRead.continuationPoint;
|
|
198
|
+
return await this.historyReadNode(context, nodeToRead, historyReadDetails, timestampsToReturn, {
|
|
199
|
+
continuationPoint,
|
|
200
|
+
releaseContinuationPoints,
|
|
201
|
+
index
|
|
202
|
+
});
|
|
203
|
+
};
|
|
204
|
+
const promises: Promise<HistoryReadResult>[] = [];
|
|
205
|
+
let index = 0;
|
|
206
|
+
for (const nodeToRead of nodesToRead) {
|
|
207
|
+
promises.push(_r(nodeToRead, index));
|
|
208
|
+
index++;
|
|
209
|
+
}
|
|
210
|
+
const result = await Promise.all(promises);
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
public async browseNode(browseDescription: BrowseDescriptionOptions, context?: ISessionContext): Promise<BrowseResult> {
|
|
215
|
+
if (!this.addressSpace) {
|
|
216
|
+
throw new Error("Address Space has not been initialized");
|
|
217
|
+
}
|
|
218
|
+
const nodeId = resolveNodeId(browseDescription.nodeId!);
|
|
219
|
+
const r = this.addressSpace.browseSingleNode(
|
|
220
|
+
nodeId,
|
|
221
|
+
browseDescription instanceof BrowseDescription
|
|
222
|
+
? browseDescription
|
|
223
|
+
: new BrowseDescription({ ...browseDescription, nodeId }),
|
|
224
|
+
context
|
|
225
|
+
);
|
|
226
|
+
return r;
|
|
227
|
+
}
|
|
228
|
+
public async readNode(
|
|
229
|
+
context: ISessionContext,
|
|
230
|
+
nodeToRead: ReadValueIdOptions,
|
|
231
|
+
maxAge: number,
|
|
232
|
+
timestampsToReturn?: TimestampsToReturn
|
|
233
|
+
): Promise<DataValue> {
|
|
234
|
+
assert(context instanceof SessionContext);
|
|
235
|
+
const nodeId = resolveNodeId(nodeToRead.nodeId!);
|
|
236
|
+
const attributeId: AttributeIds = nodeToRead.attributeId!;
|
|
237
|
+
const indexRange: NumericRange = nodeToRead.indexRange!;
|
|
238
|
+
const dataEncoding = nodeToRead.dataEncoding;
|
|
239
|
+
|
|
240
|
+
if (timestampsToReturn === TimestampsToReturn.Invalid) {
|
|
241
|
+
return new DataValue({ statusCode: StatusCodes.BadTimestampsToReturnInvalid });
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
timestampsToReturn = coerceTimestampsToReturn(timestampsToReturn);
|
|
245
|
+
|
|
246
|
+
const obj = this.__findNode(coerceNodeId(nodeId));
|
|
247
|
+
|
|
248
|
+
let dataValue;
|
|
249
|
+
if (!obj) {
|
|
250
|
+
// Object Not Found
|
|
251
|
+
return new DataValue({ statusCode: StatusCodes.BadNodeIdUnknown });
|
|
252
|
+
} else {
|
|
253
|
+
// check access
|
|
254
|
+
// BadUserAccessDenied
|
|
255
|
+
// BadNotReadable
|
|
256
|
+
// invalid attributes : BadNodeAttributesInvalid
|
|
257
|
+
// invalid range : BadIndexRangeInvalid
|
|
258
|
+
dataValue = obj.readAttribute(context, attributeId, indexRange, dataEncoding);
|
|
259
|
+
dataValue = apply_timestamps_no_copy(dataValue, timestampsToReturn, attributeId);
|
|
260
|
+
|
|
261
|
+
if (timestampsToReturn === TimestampsToReturn.Server) {
|
|
262
|
+
dataValue.sourceTimestamp = null;
|
|
263
|
+
dataValue.sourcePicoseconds = 0;
|
|
264
|
+
}
|
|
265
|
+
if (
|
|
266
|
+
(timestampsToReturn === TimestampsToReturn.Both || timestampsToReturn === TimestampsToReturn.Server) &&
|
|
267
|
+
(!dataValue.serverTimestamp || dataValue.serverTimestamp.getTime() === minOPCUADate.getTime())
|
|
268
|
+
) {
|
|
269
|
+
const t: Date = context.currentTime ? context.currentTime.timestamp : getCurrentClock().timestamp;
|
|
270
|
+
dataValue.serverTimestamp = t;
|
|
271
|
+
dataValue.serverPicoseconds = 0; // context.currentTime.picoseconds;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return dataValue;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private __findNode(nodeId: NodeId): BaseNode | null {
|
|
279
|
+
const namespaceIndex = nodeId.namespace || 0;
|
|
280
|
+
|
|
281
|
+
if (namespaceIndex && namespaceIndex >= (this.addressSpace?.getNamespaceArray().length || 0)) {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
const namespace = this.addressSpace!.getNamespace(namespaceIndex)!;
|
|
285
|
+
return namespace.findNode2(nodeId)!;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
public async writeNode(context: ISessionContext, writeValue: WriteValue): Promise<StatusCode> {
|
|
289
|
+
await resolveOpaqueOnAddressSpace(this.addressSpace!, writeValue.value.value!);
|
|
290
|
+
|
|
291
|
+
assert(context instanceof SessionContext);
|
|
292
|
+
assert(writeValue.schema.name === "WriteValue");
|
|
293
|
+
assert(writeValue.value instanceof DataValue);
|
|
294
|
+
|
|
295
|
+
if (!writeValue.value.value) {
|
|
296
|
+
/* missing Variant */
|
|
297
|
+
return StatusCodes.BadTypeMismatch;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
assert(writeValue.value.value instanceof Variant);
|
|
301
|
+
|
|
302
|
+
const nodeId = writeValue.nodeId;
|
|
303
|
+
|
|
304
|
+
const obj = this.__findNode(nodeId) as UAVariable;
|
|
305
|
+
if (!obj) {
|
|
306
|
+
return StatusCodes.BadNodeIdUnknown;
|
|
307
|
+
} else {
|
|
308
|
+
return await new Promise<StatusCode>((resolve, reject) => {
|
|
309
|
+
obj.writeAttribute(context, writeValue, (err, statusCode) => {
|
|
310
|
+
if (err) {
|
|
311
|
+
reject(err);
|
|
312
|
+
} else {
|
|
313
|
+
resolve(statusCode!);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
public async callMethod(context: ISessionContext, methodToCall: CallMethodRequest): Promise<CallMethodResultOptions> {
|
|
321
|
+
return await new Promise((resolve, reject) => {
|
|
322
|
+
callMethodHelper(context, this.addressSpace, methodToCall, (err, result) => {
|
|
323
|
+
if (err) {
|
|
324
|
+
reject(err);
|
|
325
|
+
} else {
|
|
326
|
+
resolve(result!);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
public async historyReadNode(
|
|
333
|
+
context: ISessionContext,
|
|
334
|
+
nodeToRead: HistoryReadValueId,
|
|
335
|
+
historyReadDetails: HistoryReadDetails,
|
|
336
|
+
timestampsToReturn: TimestampsToReturn,
|
|
337
|
+
continuationData: ContinuationData
|
|
338
|
+
): Promise<HistoryReadResult> {
|
|
339
|
+
assert(context instanceof SessionContext);
|
|
340
|
+
if (timestampsToReturn === TimestampsToReturn.Invalid) {
|
|
341
|
+
return new HistoryReadResult({
|
|
342
|
+
statusCode: StatusCodes.BadTimestampsToReturnInvalid
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
const nodeId = nodeToRead.nodeId;
|
|
346
|
+
const indexRange = nodeToRead.indexRange;
|
|
347
|
+
const dataEncoding = nodeToRead.dataEncoding;
|
|
348
|
+
const continuationPoint = nodeToRead.continuationPoint;
|
|
349
|
+
|
|
350
|
+
timestampsToReturn = coerceTimestampsToReturn(timestampsToReturn);
|
|
351
|
+
if (timestampsToReturn === TimestampsToReturn.Invalid) {
|
|
352
|
+
return new HistoryReadResult({ statusCode: StatusCodes.BadTimestampsToReturnInvalid });
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const obj = this.__findNode(nodeId) as UAVariable;
|
|
356
|
+
|
|
357
|
+
if (!obj) {
|
|
358
|
+
// may be return BadNodeIdUnknown in dataValue instead ?
|
|
359
|
+
// Object Not Found
|
|
360
|
+
return new HistoryReadResult({ statusCode: StatusCodes.BadNodeIdUnknown });
|
|
361
|
+
} else {
|
|
362
|
+
// istanbul ignore next
|
|
363
|
+
if (!obj.historyRead) {
|
|
364
|
+
// note : Object and View may also support historyRead to provide Event historical data
|
|
365
|
+
// todo implement historyRead for Object and View
|
|
366
|
+
const msg =
|
|
367
|
+
" this node doesn't provide historyRead! probably not a UAVariable\n " +
|
|
368
|
+
obj.nodeId.toString() +
|
|
369
|
+
" " +
|
|
370
|
+
obj.browseName.toString() +
|
|
371
|
+
"\n" +
|
|
372
|
+
"with " +
|
|
373
|
+
nodeToRead.toString() +
|
|
374
|
+
"\n" +
|
|
375
|
+
"HistoryReadDetails " +
|
|
376
|
+
historyReadDetails.toString();
|
|
377
|
+
if (doDebug) {
|
|
378
|
+
debugLog(chalk.cyan("ServerEngine#_historyReadNode "), chalk.white.bold(msg));
|
|
379
|
+
}
|
|
380
|
+
throw new Error(msg);
|
|
381
|
+
}
|
|
382
|
+
// check access
|
|
383
|
+
// BadUserAccessDenied
|
|
384
|
+
// BadNotReadable
|
|
385
|
+
// invalid attributes : BadNodeAttributesInvalid
|
|
386
|
+
// invalid range : BadIndexRangeInvalid
|
|
387
|
+
const result = await obj.historyRead(context, historyReadDetails, indexRange, dataEncoding, continuationData);
|
|
388
|
+
assert(result!.statusCode instanceof StatusCode);
|
|
389
|
+
assert(result!.isValid());
|
|
390
|
+
return result;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ISessionContext } from "node-opcua-address-space-base";
|
|
2
|
+
import { DataValue } from "node-opcua-data-value";
|
|
3
|
+
import { StatusCode } from "node-opcua-status-code";
|
|
4
|
+
import { BrowseDescriptionOptions, BrowseResult, ReadRequestOptions, WriteValue, CallMethodRequest, CallMethodResultOptions, HistoryReadRequest, HistoryReadResult } from "node-opcua-types";
|
|
5
|
+
|
|
6
|
+
export interface IAddressSpaceAccessor {
|
|
7
|
+
browse(context: ISessionContext, nodesToBrowse: BrowseDescriptionOptions[]): Promise<BrowseResult[]>;
|
|
8
|
+
read(context: ISessionContext, readRequest: ReadRequestOptions): Promise<DataValue[]>;
|
|
9
|
+
write(context: ISessionContext, nodesToWrite: WriteValue[]): Promise<StatusCode[]>;
|
|
10
|
+
call(context: ISessionContext, methodsToCall: CallMethodRequest[]): Promise<CallMethodResultOptions[]>;
|
|
11
|
+
historyRead(context: ISessionContext, historyReadRequest: HistoryReadRequest): Promise<HistoryReadResult[]>;
|
|
12
|
+
}
|
package/source/monitored_item.ts
CHANGED
|
@@ -54,7 +54,7 @@ import { sameVariant, Variant, VariantArrayType } from "node-opcua-variant";
|
|
|
54
54
|
|
|
55
55
|
import { appendToTimer, removeFromTimer } from "./node_sampler";
|
|
56
56
|
import { validateFilter } from "./validate_filter";
|
|
57
|
-
import { checkWhereClauseOnAdressSpace } from "./filter/check_where_clause_on_address_space";
|
|
57
|
+
import { checkWhereClauseOnAdressSpace as checkWhereClauseOnAddressSpace } from "./filter/check_where_clause_on_address_space";
|
|
58
58
|
import { SamplingFunc } from "./sampling_func";
|
|
59
59
|
|
|
60
60
|
export type QueueItem = MonitoredItemNotification | EventFieldList;
|
|
@@ -991,7 +991,7 @@ export class MonitoredItem extends EventEmitter {
|
|
|
991
991
|
|
|
992
992
|
const addressSpace: AddressSpace = eventData.$eventDataSource?.addressSpace as AddressSpace;
|
|
993
993
|
|
|
994
|
-
if (!
|
|
994
|
+
if (!checkWhereClauseOnAddressSpace(addressSpace, SessionContext.defaultContext, this.filter.whereClause, eventData)) {
|
|
995
995
|
return;
|
|
996
996
|
}
|
|
997
997
|
|