node-opcua-client
Version:
pure nodejs OPCUA SDK - module client
1,414 lines (1,263 loc) • 86.8 kB
text/typescript
/**
* @module node-opcua-client-private
*/
import chalk from "chalk";
import { EventEmitter } from "events";
import { assert } from "node-opcua-assert";
import type { DateTime } from "node-opcua-basic-types";
import {
type ExtraDataTypeManager,
extractDataValueToPromote,
getExtensionObjectConstructor,
getExtraDataTypeManager,
type PseudoDataValue,
promoteOpaqueStructure
} from "node-opcua-client-dynamic-extension-object";
import type { AggregateFunction } from "node-opcua-constants";
import type { Certificate, Nonce } from "node-opcua-crypto/web";
import { BrowseDirection, type LocalizedTextLike } from "node-opcua-data-model";
import { DataValue } from "node-opcua-data-value";
import { checkDebugFlag, make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug";
import type { ExtensionObject } from "node-opcua-extension-object";
import { coerceNodeId, NodeId, type NodeIdLike, resolveNodeId } from "node-opcua-nodeid";
import {
type BrowseDescriptionLike,
getArgumentDefinitionHelper,
getBuiltInDataType,
type IBasicSessionAsync2,
type IBasicTransportSettings,
type NodeAttributes,
type ResponseCallback,
readAllAttributes,
readNamespaceArray
} from "node-opcua-pseudo-session";
import type { AnyConstructorFunc } from "node-opcua-schemas";
import { ClientSecureChannelLayer, requestHandleNotSetValue, type SignatureData } from "node-opcua-secure-channel";
import { BrowseDescription, BrowseRequest, BrowseResponse, BrowseResult } from "node-opcua-service-browse";
import { CallMethodRequest, type CallMethodResult, CallRequest, CallResponse } from "node-opcua-service-call";
import type { EndpointDescription } from "node-opcua-service-endpoints";
import {
HistoryData,
HistoryReadRequest,
HistoryReadResponse,
type HistoryReadResult,
ReadProcessedDetails,
ReadRawModifiedDetails
} from "node-opcua-service-history";
import { QueryFirstRequest, QueryFirstResponse } from "node-opcua-service-query";
import {
AttributeIds,
ReadRequest,
ReadResponse,
ReadValueId,
type ReadValueIdOptions,
TimestampsToReturn
} from "node-opcua-service-read";
import {
RegisterNodesRequest,
RegisterNodesResponse,
UnregisterNodesRequest,
UnregisterNodesResponse
} from "node-opcua-service-register-node";
import {
CreateMonitoredItemsRequest,
CreateMonitoredItemsResponse,
CreateSubscriptionRequest,
CreateSubscriptionResponse,
DeleteMonitoredItemsRequest,
DeleteMonitoredItemsResponse,
DeleteSubscriptionsRequest,
DeleteSubscriptionsResponse,
ModifyMonitoredItemsRequest,
ModifyMonitoredItemsResponse,
ModifySubscriptionRequest,
ModifySubscriptionResponse,
PublishRequest,
PublishResponse,
RepublishRequest,
RepublishResponse,
SetMonitoringModeRequest,
SetMonitoringModeResponse,
SetPublishingModeRequest,
SetPublishingModeResponse,
SetTriggeringRequest,
type SetTriggeringRequestOptions,
SetTriggeringResponse,
TransferSubscriptionsRequest,
TransferSubscriptionsResponse
} from "node-opcua-service-subscription";
import {
type BrowsePath,
type BrowsePathResult,
TranslateBrowsePathsToNodeIdsRequest,
TranslateBrowsePathsToNodeIdsResponse
} from "node-opcua-service-translate-browse-path";
import { WriteRequest, WriteResponse, WriteValue } from "node-opcua-service-write";
import { type Callback, type CallbackT, type ErrorCallback, type StatusCode, StatusCodes } from "node-opcua-status-code";
import {
ActivateSessionRequest,
type AggregateConfigurationOptions,
BrowseNextRequest,
BrowseNextResponse,
CloseSessionRequest,
type HistoryReadValueIdOptions,
UserTokenType,
type WriteValueOptions
} from "node-opcua-types";
import { buffer_ellipsis, getFunctionParameterNames, isNullOrUndefined } from "node-opcua-utils";
import { DataType, type Variant, type VariantLike } from "node-opcua-variant";
import type {
ArgumentDefinition,
CallMethodRequestLike,
ClientSession,
CreateMonitoredItemsRequestLike,
CreateSubscriptionRequestLike,
DeleteMonitoredItemsRequestLike,
DeleteSubscriptionsRequestLike,
ExtraReadHistoryValueParameters,
HistoryReadValueIdOptions2,
MethodId,
ModifyMonitoredItemsRequestLike,
ModifySubscriptionRequestLike,
MonitoredItemData,
QueryFirstRequestLike,
SetMonitoringModeRequestLike,
SubscriptionId,
TransferSubscriptionsRequestLike
} from "../client_session";
import { ClientSessionKeepAliveManager } from "../client_session_keepalive_manager";
import type { ClientSubscription } from "../client_subscription";
import type { Request, Response } from "../common";
import type { UserIdentityInfo } from "../user_identity_info";
import { ClientSidePublishEngine } from "./client_publish_engine";
import { ClientSubscriptionImpl } from "./client_subscription_impl";
import type { IClientBase } from "./i_private_client";
import { repair_client_session } from "./reconnection/reconnection";
const helpAPIChange = process.env.DEBUG && process.env.DEBUG.match(/API/);
const debugLog = make_debugLog(__filename);
const doDebug = checkDebugFlag(__filename);
const warningLog = make_warningLog(__filename);
const errorLog = make_errorLog(__filename);
let pendingTransactionMessageDisplayed = false;
function coerceBrowseDescription(data: any): BrowseDescription {
if (typeof data === "string" || data instanceof NodeId) {
return coerceBrowseDescription({
browseDirection: BrowseDirection.Forward,
includeSubtypes: true,
nodeClassMask: 0,
nodeId: data,
referenceTypeId: "HierarchicalReferences",
resultMask: 63
});
} else {
data.nodeId = resolveNodeId(data.nodeId);
data.referenceTypeId = data.referenceTypeId ? resolveNodeId(data.referenceTypeId) : null;
return new BrowseDescription(data);
}
}
function coerceReadValueId(node: any): ReadValueId {
if (typeof node === "string" || node instanceof NodeId) {
return new ReadValueId({
attributeId: AttributeIds.Value,
dataEncoding: undefined, // {namespaceIndex: 0, name: undefined}
indexRange: undefined,
nodeId: resolveNodeId(node)
});
} else {
assert(node instanceof Object);
return new ReadValueId(node);
}
}
const emptyUint32Array = new Uint32Array(0);
type EmptyCallback = (err?: Error) => void;
export interface Reconnectable {
_reconnecting: {
reconnecting: boolean;
pendingCallbacks: EmptyCallback[];
pendingTransactions: { request: Request; callback: (err: Error | null, response?: Response) => void }[];
};
}
/**
* @class ClientSession
*/
export class ClientSessionImpl extends EventEmitter implements ClientSession, Reconnectable {
static reconnectingElement: WeakMap<ClientSessionImpl, Reconnectable> = new WeakMap();
public timeout: number;
public authenticationToken?: NodeId;
public requestedMaxReferencesPerNode: number;
public sessionId: NodeId;
public lastRequestSentTime: Date;
public lastResponseReceivedTime: Date;
public serverCertificate: Certificate;
public userIdentityInfo?: UserIdentityInfo;
public name = "";
public serverNonce?: Nonce;
public serverSignature?: SignatureData; // todo : remove ?
public serverEndpoints: EndpointDescription[] = [];
public _client: IClientBase | null;
public _closed: boolean;
public _reconnecting: {
reconnecting: boolean;
pendingCallbacks: EmptyCallback[];
pendingTransactions: any[];
};
/**
* @internal
*/
public _closeEventHasBeenEmitted: boolean;
private _publishEngine: ClientSidePublishEngine | null;
private _keepAliveManager?: ClientSessionKeepAliveManager;
private $$namespaceArray?: string[];
private recursive_repair_detector = 0;
constructor(client: IClientBase) {
super();
this.serverCertificate = Buffer.alloc(0);
this.sessionId = new NodeId();
this._closeEventHasBeenEmitted = false;
this._client = client;
this._publishEngine = null;
this._closed = false;
this._reconnecting = {
reconnecting: false,
pendingCallbacks: [],
pendingTransactions: []
};
this.requestedMaxReferencesPerNode = 10000;
this.lastRequestSentTime = new Date(1, 1, 1970);
this.lastResponseReceivedTime = new Date(1, 1, 1970);
this.timeout = 0;
}
getTransportSettings(): IBasicTransportSettings {
return this._client!.getTransportSettings();
}
/**
* the endpoint on which this session is operating
* @property endpoint
* @type {EndpointDescription}
*/
get endpoint(): EndpointDescription {
return this._client!.endpoint!;
}
get subscriptionCount(): number {
return this._publishEngine ? this._publishEngine.subscriptionCount : 0;
}
get isReconnecting(): boolean {
return this._client ? this._client.isReconnecting || this._reconnecting?.reconnecting : false;
}
protected resolveNodeId(nodeId: NodeIdLike): NodeIdLike {
return resolveNodeId(nodeId);
}
public getPublishEngine(): ClientSidePublishEngine {
if (!this._publishEngine) {
this._publishEngine = new ClientSidePublishEngine(this);
}
return this._publishEngine!;
}
public changeUser(userIdentityInfo: UserIdentityInfo): Promise<StatusCode>;
public changeUser(userIdentityInfo: UserIdentityInfo, callback: CallbackT<StatusCode>): void;
public changeUser(userIdentityInfo: UserIdentityInfo, callback?: CallbackT<StatusCode>): any {
userIdentityInfo = userIdentityInfo || {
type: UserTokenType.Anonymous
};
if (!this._client || !this.userIdentityInfo) {
warningLog("changeUser: invalid session");
return callback!(null, StatusCodes.BadInternalError);
}
const old_userIdentity: UserIdentityInfo = this.userIdentityInfo;
this._client._activateSession(this, userIdentityInfo, (err1: Error | null, session2?: ClientSessionImpl) => {
if (err1) {
this.userIdentityInfo = old_userIdentity;
warningLog("activate session error = ", err1.message);
return callback!(null, StatusCodes.BadUserAccessDenied);
}
this.userIdentityInfo = userIdentityInfo;
callback!(null, StatusCodes.Good);
});
}
/**
*
* @example
*
* ```javascript
* session.browse("RootFolder",function(err,browseResult) {
* if(err) return callback(err);
* console.log(browseResult.toString());
* callback();
* } );
* ```
*
*
* @example
*
* ``` javascript
* const browseDescription = {
* nodeId: "ObjectsFolder",
* referenceTypeId: "Organizes",
* browseDirection: BrowseDirection.Inverse,
* includeSubtypes: true,
* nodeClassMask: 0,
* resultMask: 63
* }
* session.browse(browseDescription,function(err, browseResult) {
* if(err) return callback(err);
* console.log(browseResult.toString());
* callback();
* });
* ```
* @example
*
* ``` javascript
* session.browse([ "RootFolder", "ObjectsFolder"],function(err, browseResults) {
* assert(browseResults.length === 2);
* });
* ```
*
* @example
* ``` javascript
* const browseDescriptions = [
* {
* nodeId: "ObjectsFolder",
* referenceTypeId: "Organizes",
* browseDirection: BrowseDirection.Inverse,
* includeSubtypes: true,
* nodeClassMask: 0,
* resultMask: 63
* },
* // {...}
* ]
* session.browse(browseDescriptions,function(err, browseResults) {
*
* });
* ```
*
*
*/
public browse(nodeToBrowse: BrowseDescriptionLike, callback: ResponseCallback<BrowseResult>): void;
public browse(nodesToBrowse: BrowseDescriptionLike[], callback: ResponseCallback<BrowseResult[]>): void;
public async browse(nodeToBrowse: BrowseDescriptionLike): Promise<BrowseResult>;
public async browse(nodesToBrowse: BrowseDescriptionLike[]): Promise<BrowseResult[]>;
/**
* @internal
* @param args
*/
public browse(...args: any[]): any {
const arg0 = args[0];
const isArray = Array.isArray(arg0);
const callback: ResponseCallback<BrowseResult[] | BrowseResult> = args[1];
assert(typeof callback === "function");
assert(isFinite(this.requestedMaxReferencesPerNode));
const nodesToBrowse: BrowseDescription[] = (isArray ? arg0 : [arg0 as BrowseDescription]).map(coerceBrowseDescription);
const request = new BrowseRequest({
nodesToBrowse,
requestedMaxReferencesPerNode: this.requestedMaxReferencesPerNode
});
this.performMessageTransaction(request, (err: Error | null, response?: Response) => {
if (err) {
return callback(err);
}
/* c8 ignore next */
if (!response || !(response instanceof BrowseResponse)) {
return callback(new Error("Internal Error"));
}
const results: BrowseResult[] = response.results ? response.results : [];
if (this.requestedMaxReferencesPerNode > 0) {
for (let i = 0; i < results.length; i++) {
const r = results[i];
/* c8 ignore next */
if (r.references && r.references.length > this.requestedMaxReferencesPerNode) {
warningLog(
chalk.yellow("warning") +
" BrowseResponse : the server didn't take into" +
" account our requestedMaxReferencesPerNode "
);
warningLog(" this.requestedMaxReferencesPerNode= " + this.requestedMaxReferencesPerNode);
warningLog(" got " + r.references.length + "for " + nodesToBrowse[i].nodeId.toString());
warningLog(" continuationPoint ", r.continuationPoint);
}
}
}
for (const r of results) {
r.references = r.references || /* c8 ignore next */ [];
}
assert(results[0] instanceof BrowseResult);
return callback(null, isArray ? results : results[0]);
});
}
public browseNext(
continuationPoint: Buffer,
releaseContinuationPoints: boolean,
callback: ResponseCallback<BrowseResult>
): void;
public browseNext(
continuationPoints: Buffer[],
releaseContinuationPoints: boolean,
callback: ResponseCallback<BrowseResult[]>
): void;
public async browseNext(continuationPoint: Buffer, releaseContinuationPoints: boolean): Promise<BrowseResult>;
public async browseNext(continuationPoints: Buffer[], releaseContinuationPoints: boolean): Promise<BrowseResult[]>;
public browseNext(...args: any[]): any {
const arg0 = args[0];
const isArray = Array.isArray(arg0);
const releaseContinuationPoints = args[1] as boolean;
const callback: any = args[2];
assert(typeof callback === "function", "expecting a callback function here");
const continuationPoints: Buffer[] = isArray ? arg0 : [arg0 as Buffer];
const request = new BrowseNextRequest({
continuationPoints,
releaseContinuationPoints
});
this.performMessageTransaction(request, (err: Error | null, response?: Response) => {
/* c8 ignore next */
if (err) {
return callback(err);
}
/* c8 ignore next */
if (!response || !(response instanceof BrowseNextResponse)) {
return callback(new Error("Internal Error"));
}
const results: BrowseResult[] = response.results ? response.results : [];
for (const r of results) {
r.references = r.references || [];
}
assert(results[0] instanceof BrowseResult);
return callback(null, isArray ? results : results[0]);
});
}
/**
*
* @example
*
* ```javascript
* session.readVariableValue("ns=2;s=Furnace_1.Temperature",function(err,dataValue) {
* if(err) { return callback(err); }
* if (dataValue.isGood()) {
* }
* console.log(dataValue.toString());
* callback();
* });
* ```
*
* @example
*
* ```javascript
* session.readVariableValue(["ns=0;i=2257","ns=0;i=2258"],function(err,dataValues) {
* if (!err) {
* console.log(dataValues[0].toString());
* console.log(dataValues[1].toString());
* }
* });
* ```
*
* @example
* ```javascript
* const dataValues = await session.readVariableValue(["ns=1;s=Temperature","ns=1;s=Pressure"]);
* ```
*
* @deprecated
*/
public readVariableValue(nodeId: NodeIdLike, callback: ResponseCallback<DataValue>): void;
public readVariableValue(nodeIds: NodeIdLike[], callback: ResponseCallback<DataValue[]>): void;
public async readVariableValue(nodeId: NodeIdLike): Promise<DataValue>;
public async readVariableValue(nodeIds: NodeIdLike[]): Promise<DataValue[]>;
/**
* @internal
* @param args
*/
public readVariableValue(...args: any[]): any {
const callback = args[1];
assert(typeof callback === "function");
const isArray = Array.isArray(args[0]);
const nodes = isArray ? args[0] : [args[0]];
const nodesToRead = nodes.map(coerceReadValueId);
const request = new ReadRequest({
nodesToRead,
timestampsToReturn: TimestampsToReturn.Neither
});
this.performMessageTransaction(request, (err: Error | null, response?: Response) => {
/* c8 ignore next */
if (err) {
return callback(err);
}
/* c8 ignore next */
if (!(response instanceof ReadResponse)) {
return callback(new Error("Internal Error"));
}
/* c8 ignore next */
if (response.responseHeader.serviceResult.isNot(StatusCodes.Good)) {
return callback(new Error(response.responseHeader.serviceResult.toString()));
}
/* c8 ignore next */
if (!response.results) {
response.results = [];
}
assert(nodes.length === response.results.length);
callback(null, isArray ? response.results : response.results[0]);
});
}
/**
*
* @example
*
* ```javascript
* // es5
* session.readHistoryValue(
* "ns=5;s=Simulation Examples.Functions.Sine1",
* "2015-06-10T09:00:00.000Z",
* "2015-06-10T09:01:00.000Z", function(err,dataValues) {
*
* });
* ```
*
* ```javascript
* // es6
* const dataValues = await session.readHistoryValue(
* "ns=5;s=Simulation Examples.Functions.Sine1",
* "2015-06-10T09:00:00.000Z",
* "2015-06-10T09:01:00.000Z");
* ```
* @param nodeToRead the read value id
* @param start the start time in UTC format
* @param end the end time in UTC format
* @param callback
*/
public readHistoryValue(
nodesToRead: NodeIdLike[] | HistoryReadValueIdOptions2[],
start: DateTime,
end: DateTime,
callback: (err: Error | null, results?: HistoryReadResult[]) => void
): void;
public readHistoryValue(
nodesToRead: NodeIdLike[] | HistoryReadValueIdOptions2[],
start: DateTime,
end: DateTime,
options: ExtraReadHistoryValueParameters | undefined,
callback: (err: Error | null, results?: HistoryReadResult[]) => void
): void;
public async readHistoryValue(
nodesToRead: NodeIdLike[] | HistoryReadValueIdOptions2[],
start: DateTime,
end: DateTime,
options?: ExtraReadHistoryValueParameters
): Promise<HistoryReadResult[]>;
public readHistoryValue(
nodeToRead: NodeIdLike | HistoryReadValueIdOptions2,
start: DateTime,
end: DateTime,
callback: (err: Error | null, results?: HistoryReadResult) => void
): void;
public readHistoryValue(
nodeToRead: NodeIdLike | HistoryReadValueIdOptions2,
start: DateTime,
end: DateTime,
options: ExtraReadHistoryValueParameters | undefined,
callback: (err: Error | null, results?: HistoryReadResult) => void
): void;
public async readHistoryValue(
nodeToRead: NodeIdLike | HistoryReadValueIdOptions2,
start: DateTime,
end: DateTime,
parameters: ExtraReadHistoryValueParameters
): Promise<HistoryReadResult>;
public readHistoryValue(...args: any[]): any {
const startTime = args[1];
const endTime = args[2];
let options: ExtraReadHistoryValueParameters = {};
let callback = args[3];
if (typeof callback !== "function") {
options = args[3];
callback = args[4];
}
assert(typeof callback === "function");
// adjust parameters
options.numValuesPerNode = options.numValuesPerNode || 0;
options.returnBounds = options.returnBounds || options.returnBounds === undefined ? true : false;
options.isReadModified = options.isReadModified || false;
options.timestampsToReturn = options.timestampsToReturn ?? TimestampsToReturn.Both;
const arg0 = args[0];
const isArray = Array.isArray(arg0);
const nodes = isArray ? arg0 : [arg0];
const nodesToRead: HistoryReadValueIdOptions[] = [];
for (const node of nodes) {
if (!node.nodeId) {
nodesToRead.push({
continuationPoint: undefined,
dataEncoding: undefined, // {namespaceIndex: 0, name: undefined},
indexRange: undefined,
nodeId: this.resolveNodeId(node as NodeIdLike)
});
} else {
nodesToRead.push(node as HistoryReadValueIdOptions);
}
}
const readRawModifiedDetails = new ReadRawModifiedDetails({
endTime,
isReadModified: false,
numValuesPerNode: options.numValuesPerNode,
returnBounds: options.returnBounds,
startTime
});
const request = new HistoryReadRequest({
historyReadDetails: readRawModifiedDetails,
nodesToRead,
releaseContinuationPoints: false,
timestampsToReturn: options.timestampsToReturn
});
request.nodesToRead = request.nodesToRead || [];
assert(nodes.length === request.nodesToRead.length);
this.historyRead(request, (err: Error | null, response?: HistoryReadResponse) => {
/* c8 ignore next */
if (err) {
return callback(err);
}
/* c8 ignore next */
if (!response || !(response instanceof HistoryReadResponse)) {
return callback(new Error("Internal Error"));
}
response.results = response.results || [];
assert(nodes.length === response.results.length);
callback(null, isArray ? response.results : response.results[0]);
});
}
public historyRead(request: HistoryReadRequest, callback: Callback<HistoryReadResponse>): void;
public historyRead(request: HistoryReadRequest): Promise<HistoryReadResponse>;
public historyRead(request: HistoryReadRequest, callback?: CallbackT<HistoryReadResponse>): any {
/* c8 ignore next */
if (!callback) {
throw new Error("expecting a callback");
}
this.performMessageTransaction(request, (err: Error | null, response) => {
/* c8 ignore next */
if (err) {
return callback(err);
}
/* c8 ignore next */
if (!response || !(response instanceof HistoryReadResponse)) {
return callback(new Error("Internal Error"));
}
if (response.responseHeader.serviceResult.isNot(StatusCodes.Good)) {
return callback(new Error(response.responseHeader.serviceResult.toString()));
}
response.results = response.results || /* c8 ignore next */ [];
// perform ExtensionObject resolution
const promises = response.results.map(async (result) => {
if (result.historyData && result.historyData instanceof HistoryData) {
if (result.historyData.dataValues) {
await promoteOpaqueStructure(this, result.historyData.dataValues);
}
}
});
Promise.all(promises)
.then(() => {
callback(null, response);
})
.catch((err) => callback(err));
});
}
public readAggregateValue(
nodesToRead: HistoryReadValueIdOptions[],
startTime: DateTime,
endTime: DateTime,
aggregateFn: AggregateFunction[],
processingInterval: number,
callback: Callback<HistoryReadResult[]>
): void;
public async readAggregateValue(
nodesToRead: HistoryReadValueIdOptions[],
startTime: DateTime,
endTime: DateTime,
aggregateFn: AggregateFunction[],
processingInterval: number
): Promise<HistoryReadResult[]>;
public readAggregateValue(
nodeToRead: HistoryReadValueIdOptions,
startTime: DateTime,
endTime: DateTime,
aggregateFn: AggregateFunction,
processingInterval: number,
callback: Callback<HistoryReadResult>
): void;
public async readAggregateValue(
nodeToRead: HistoryReadValueIdOptions,
startTime: DateTime,
endTime: DateTime,
aggregateFn: AggregateFunction,
processingInterval: number
): Promise<HistoryReadResult>;
public readAggregateValue(
nodesToRead: HistoryReadValueIdOptions[],
startTime: DateTime,
endTime: DateTime,
aggregateFn: AggregateFunction[],
processingInterval: number,
aggregateConfiguration: AggregateConfigurationOptions,
callback: Callback<HistoryReadResult[]>
): void;
public async readAggregateValue(
nodesToRead: HistoryReadValueIdOptions[],
startTime: DateTime,
endTime: DateTime,
aggregateFn: AggregateFunction[],
processingInterval: number,
aggregateConfiguration: AggregateConfigurationOptions
): Promise<HistoryReadResult[]>;
public readAggregateValue(
nodeToRead: HistoryReadValueIdOptions,
startTime: DateTime,
endTime: DateTime,
aggregateFn: AggregateFunction,
processingInterval: number,
aggregateConfiguration: AggregateConfigurationOptions,
callback: Callback<HistoryReadResult>
): void;
public async readAggregateValue(
nodeToRead: HistoryReadValueIdOptions,
startTime: DateTime,
endTime: DateTime,
aggregateFn: AggregateFunction,
processingInterval: number,
aggregateConfiguration: AggregateConfigurationOptions
): Promise<HistoryReadResult>;
public readAggregateValue(
arg0: HistoryReadValueIdOptions[] | HistoryReadValueIdOptions,
startTime: DateTime,
endTime: DateTime,
aggregateFn: AggregateFunction[] | AggregateFunction,
processingInterval: number,
...args: any[]
): any {
const callback = typeof args[0] === "function" ? args[0] : args[1];
assert(typeof callback === "function");
const defaultAggregateFunction = {
percentDataBad: 100,
percentDataGood: 100,
treatUncertainAsBad: true,
useServerCapabilitiesDefaults: true,
useSlopedExtrapolation: false
};
const aggregateConfiguration = typeof args[0] === "function" ? defaultAggregateFunction : args[0];
const isArray = Array.isArray(arg0);
const nodesToRead: HistoryReadValueIdOptions[] = isArray
? (arg0 as HistoryReadValueIdOptions[])
: [arg0 as HistoryReadValueIdOptions];
const aggregateFns: AggregateFunction[] = Array.isArray(aggregateFn)
? (aggregateFn as AggregateFunction[])
: [aggregateFn as AggregateFunction];
assert(aggregateFns.length === nodesToRead.length);
const readProcessedDetails = new ReadProcessedDetails({
aggregateType: aggregateFns,
endTime,
processingInterval,
startTime,
aggregateConfiguration
});
const request = new HistoryReadRequest({
historyReadDetails: readProcessedDetails,
nodesToRead,
releaseContinuationPoints: false,
timestampsToReturn: TimestampsToReturn.Both
});
assert(nodesToRead.length === request.nodesToRead!.length);
this.performMessageTransaction(request, (err: Error | null, response) => {
/* c8 ignore next */
if (err) {
return callback(err);
}
/* c8 ignore next */
if (!response || !(response instanceof HistoryReadResponse)) {
return callback(new Error("Internal Error"));
}
if (response.responseHeader.serviceResult.isNot(StatusCodes.Good)) {
return callback(new Error(response.responseHeader.serviceResult.toString()));
}
response.results = response.results || /* c8 ignore next */ [];
assert(nodesToRead.length === response.results.length);
callback(null, isArray ? response.results : response.results[0]);
});
}
/**
*
* @param nodesToWrite {WriteValue[]} - the array of value to write. One or more elements.
* @param {Function} callback - the callback function
* @param callback.err {object|null} the error if write has failed or null if OK
* @param callback.statusCodes {StatusCode[]} - an array of status code of each write
*
* @example
*
* const nodesToWrite = [
* {
* nodeId: "ns=1;s=SetPoint1",
* attributeId: opcua.AttributeIds.Value,
* value: {
* statusCode: Good,
* value: {
* dataType: opcua.DataType.Double,
* value: 100.0
* }
* }
* },
* {
* nodeId: "ns=1;s=SetPoint2",
* attributeIds opcua.AttributeIds.Value,
* value: {
* statusCode: Good,
* value: {
* dataType: opcua.DataType.Double,
* value: 45.0
* }
* }
* }
* ];
* session.write(nodesToWrite,function (err,statusCodes) {
* if(err) { return callback(err);}
* //
* });
*
* @param nodeToWrite {WriteValue} - the value to write
* @param callback - the callback function
* @param callback.err {object|null} the error if write has failed or null if OK
* @param callback.statusCode {StatusCodes} - the status code of the write
*
* @example
*
* const nodeToWrite = {
* nodeId: "ns=1;s=SetPoint",
* attributeId: opcua.AttributeIds.Value,
* value: {
* statusCode: Good,
* value: {
* dataType: opcua.DataType.Double,
* value: 100.0
* }
* }
* };
* session.write(nodeToWrite,function (err,statusCode) {
* if(err) { return callback(err);}
* //
* });
*
*
* @param nodeToWrite {WriteValue} - the value to write
* @return {Promise<StatusCode>}
*
* @example
*
* ```javascript
* session.write(nodeToWrite).then(function(statusCode) { });
* ```
*
* @example
*
* ```javascript
* const statusCode = await session.write(nodeToWrite);
* ```
*
* @param nodesToWrite {Array<WriteValue>} - the value to write
* @return {Promise<Array<StatusCode>>}
*
* @example
* ```javascript
* session.write(nodesToWrite).then(function(statusCodes) { });
* ```
*
* @example
* ```javascript
* const statusCodes = await session.write(nodesToWrite);
* ```
*/
public write(nodeToWrite: WriteValueOptions, callback: ResponseCallback<StatusCode>): void;
public write(nodesToWrite: WriteValueOptions[], callback: ResponseCallback<StatusCode[]>): void;
public async write(nodesToWrite: WriteValueOptions[]): Promise<StatusCode[]>;
public async write(nodeToWrite: WriteValueOptions): Promise<StatusCode>;
/**
* @internal
* @param args
*/
public write(...args: any[]): any {
const arg0 = args[0];
const isArray = Array.isArray(arg0);
const nodesToWrite = isArray ? arg0 : [arg0];
const callback = args[1];
assert(typeof callback === "function");
const request = new WriteRequest({ nodesToWrite });
this.performMessageTransaction(request, (err: Error | null, response?: Response) => {
/* c8 ignore next */
if (err) {
return callback(err, response);
}
/* c8 ignore next */
if (!response || !(response instanceof WriteResponse)) {
return callback(new Error("Internal Error"));
}
/* c8 ignore next */
if (response.responseHeader.serviceResult.isNot(StatusCodes.Good)) {
return callback(new Error(response.responseHeader.serviceResult.toString()));
}
response.results = response.results || /* c8 ignore next */ [];
assert(nodesToWrite.length === response.results.length);
callback(null, isArray ? response.results : response.results[0]);
});
}
/**
* @deprecated use session.write instead
*
* @param nodeId {NodeId} - the node id of the node to write
* @param value {Variant} - the value to write
* @param callback {Function}
* @param callback.err {object|null} the error if write has failed or null if OK
* @param callback.statusCode {StatusCode} - the status code of the write
*
* @param nodeId {NodeId} - the node id of the node to write
* @param value {Variant} - the value to write
* @return {Promise<StatusCode>} - the status code of the write
*
*
* @example
* // please use session.write instead of session.writeSingleNode
* // as follow
* const statusCode = await session.write({
* nodeId,
* attributeId: AttributeIds.Value,
* value: {
* statusCode: Good,
* sourceTimestamp: new Date(), // optional, some server may not accept this
* value: {
* dataType: opcua.DataType.Double,
* value: 100.0
* }
* }
* });
*
*
*/
public writeSingleNode(nodeId: NodeIdLike, value: VariantLike, callback: ResponseCallback<StatusCode>): void;
public writeSingleNode(nodeId: NodeIdLike, value: VariantLike): Promise<StatusCode>;
public writeSingleNode(...args: any[]): any {
const nodeId = args[0] as NodeIdLike;
const value = args[1] as VariantLike;
const callback = args[2];
assert(typeof callback === "function");
const nodeToWrite = new WriteValue({
attributeId: AttributeIds.Value,
indexRange: undefined,
nodeId: this.resolveNodeId(nodeId),
value: new DataValue({ value })
});
this.write(nodeToWrite, (err, statusCode) => {
/* c8 ignore next */
if (err) {
return callback(err);
}
assert(statusCode);
callback(null, statusCode);
});
}
/**
*
* @example
*
*
* ``` javascript
* session.readAllAttributes("ns=2;s=Furnace_1.Temperature",function(err,data) {
* if(data.statusCode.isGood()) {
* console.log(" nodeId = ",data.nodeId.toString());
* console.log(" browseName = ",data.browseName.toString());
* console.log(" description = ",data.description.toString());
* console.log(" value = ",data.value.toString()));
* }
* });
* ```
*
* @param nodes array of nodeId to read
* @param node nodeId to read
* @param callback
*/
public readAllAttributes(node: NodeIdLike, callback: (err: Error | null, data?: NodeAttributes) => void): void;
public readAllAttributes(nodes: NodeIdLike[], callback: (err: Error | null, data?: NodeAttributes[]) => void): void;
public readAllAttributes(...args: any[]): void {
const nodes = args[0] as NodeIdLike[];
const callback = args[1] as (err: Error | null, data?: NodeAttributes[]) => void;
readAllAttributes(this, nodes)
.then((data: any) => callback(null, data))
.catch((err: Error) => callback(err));
}
/**
*
*
* @example
*
* ```javascript
* ```
*
* form1: reading a single node
*
* ``` javascript
* const nodeToRead = {
* nodeId: "ns=2;s=Furnace_1.Temperature",
* attributeId: AttributeIds.BrowseName
* };
*
* session.read(nodeToRead,function(err,dataValue) {
* if (!err) {
* console.log(dataValue.toString());
* }
* });
* ```
*
*
* @param nodesToRead {Array<ReadValueId>} - an array of nodeId to read or a ReadValueId
* @param [maxAge] {Number}
* @param callback {Function} - the callback function
* @param callback.err {Error|null} - the error or null if the transaction was OK}
* @param callback.dataValues {Array<DataValue>}
*
* @example
*
* ``` javascript
* const nodesToRead = [
* {
* nodeId: "ns=2;s=Furnace_1.Temperature",
* attributeId: AttributeIds.BrowseName
* }
* ];
* session.read(nodesToRead,function(err,dataValues) {
* if (!err) {
* dataValues.forEach(dataValue=>console.log(dataValue.toString()));
* }
* });
* ```
*
*/
public read(nodeToRead: ReadValueIdOptions, maxAge: number, callback: ResponseCallback<DataValue>): void;
public read(nodesToRead: ReadValueIdOptions[], maxAge: number, callback: ResponseCallback<DataValue[]>): void;
public read(nodeToRead: ReadValueIdOptions, callback: ResponseCallback<DataValue>): void;
public read(nodesToRead: ReadValueIdOptions[], callback: ResponseCallback<DataValue[]>): void;
public read(nodeToRead: ReadValueIdOptions, maxAge?: number): Promise<DataValue>;
public read(nodeToRead: ReadValueIdOptions[], maxAge?: number): Promise<DataValue[]>;
/**
* @internal
* @param args
*/
public read(...args: any[]): any {
if (args.length === 2) {
return this.read(args[0], 0, args[1]);
}
assert(args.length === 3);
const isArray = Array.isArray(args[0]);
const nodesToRead = isArray ? args[0] : [args[0]];
assert(Array.isArray(nodesToRead));
const maxAge = args[1];
const callback = args[2];
assert(typeof callback === "function");
/* c8 ignore next */
if (helpAPIChange) {
// the read method deprecation detection and warning
if (
!(getFunctionParameterNames(callback)[1] === "dataValues" || getFunctionParameterNames(callback)[1] === "dataValue")
) {
warningLog(chalk.red("[NODE-OPCUA-E04] the ClientSession#read API has changed !!, please fix the client code"));
warningLog(chalk.red(" replace ..:"));
warningLog(chalk.cyan(" session.read(nodesToRead,function(err,nodesToRead,results) {}"));
warningLog(chalk.red(" with .... :"));
warningLog(chalk.cyan(" session.read(nodesToRead,function(err,dataValues) {}"));
warningLog("");
warningLog(
chalk.yellow(
"please make sure to refactor your code and check that " +
"the second argument of your callback function is named"
),
chalk.cyan("dataValue" + (isArray ? "s" : ""))
);
warningLog(chalk.cyan("to make this exception disappear"));
throw new Error("ERROR ClientSession#read API has changed !!, please fix the client code");
}
}
// coerce nodeIds
for (const node of nodesToRead) {
node.nodeId = this.resolveNodeId(node.nodeId);
}
const request = new ReadRequest({
maxAge,
nodesToRead,
timestampsToReturn: TimestampsToReturn.Both
});
this.performMessageTransaction(request, (err: Error | null, response?: Response) => {
/* c8 ignore next */
if (err) {
return callback(err, response);
}
/* c8 ignore next */
if (!response || !(response instanceof ReadResponse)) {
return callback(new Error("Internal Error"));
}
// perform ExtensionObject resolution
promoteOpaqueStructure(this, response.results!)
.then(() => {
response.results = response.results || /* c8 ignore next */ [];
callback(null, isArray ? response.results : response.results[0]);
})
.catch((err) => {
callback(err);
});
});
}
public emitCloseEvent(statusCode: StatusCode): void {
if (!this._closeEventHasBeenEmitted) {
debugLog("ClientSession#emitCloseEvent");
this._closeEventHasBeenEmitted = true;
this.emit("session_closed", statusCode);
}
}
public createSubscription(
options: CreateSubscriptionRequestLike,
callback?: ResponseCallback<CreateSubscriptionResponse>
): any {
this._defaultRequest(CreateSubscriptionRequest, CreateSubscriptionResponse, options, callback);
}
/**
* @param createSubscriptionRequest
* @param callback
*
*
* subscription.on("error', function(err){ ... });
* subscription.on("terminate',function(err){ ... });
* const monitoredItem = await subscription.monitor(itemToMonitor,monitoringParameters,requestedParameters);
* monitoredItem.on("changed",function( dataValue) {...});
*
*/
public async createSubscription2(createSubscriptionRequest: CreateSubscriptionRequestLike): Promise<ClientSubscription>;
public createSubscription2(
createSubscriptionRequest: CreateSubscriptionRequestLike,
callback: (err: Error | null, subscription?: ClientSubscription) => void
): void;
public createSubscription2(...args: any[]): any {
const createSubscriptionRequest = args[0] as CreateSubscriptionRequestLike;
let callback = args[1];
const subscription = new ClientSubscriptionImpl(this, createSubscriptionRequest);
// tslint:disable-next-line:no-empty
subscription.on("error", (err) => {
if (callback) {
callback(err);
callback = null;
}
});
subscription.on("started", () => {
assert(subscription.session === this, "expecting a session here");
if (callback) {
callback(null, subscription);
callback = null;
}
});
}
public deleteSubscriptions(
options: DeleteSubscriptionsRequestLike,
callback?: ResponseCallback<DeleteSubscriptionsResponse>
): any {
this._defaultRequest(DeleteSubscriptionsRequest, DeleteSubscriptionsResponse, options, callback);
}
public setTriggering(request: SetTriggeringRequestOptions, callback?: ResponseCallback<SetTriggeringResponse>): any {
this._defaultRequest(SetTriggeringRequest, SetTriggeringResponse, request, callback);
}
/**
*/
public transferSubscriptions(
options: TransferSubscriptionsRequestLike,
callback?: ResponseCallback<TransferSubscriptionsResponse>
): any {
this._defaultRequest(TransferSubscriptionsRequest, TransferSubscriptionsResponse, options, callback);
}
public createMonitoredItems(
options: CreateMonitoredItemsRequestLike,
callback?: ResponseCallback<CreateMonitoredItemsResponse>
): any {
this._defaultRequest(CreateMonitoredItemsRequest, CreateMonitoredItemsResponse, options, callback);
}
public modifyMonitoredItems(
options: ModifyMonitoredItemsRequestLike,
callback?: ResponseCallback<ModifyMonitoredItemsResponse>
): any {
this._defaultRequest(ModifyMonitoredItemsRequest, ModifyMonitoredItemsResponse, options, callback);
}
/**
*
*/
public modifySubscription(
options: ModifySubscriptionRequestLike,
callback?: ResponseCallback<ModifySubscriptionResponse>
): any {
this._defaultRequest(ModifySubscriptionRequest, ModifySubscriptionResponse, options, callback);
}
public setMonitoringMode(options: SetMonitoringModeRequestLike, callback?: ResponseCallback<SetMonitoringModeResponse>): any {
this._defaultRequest(SetMonitoringModeRequest, SetMonitoringModeResponse, options, callback);
}
/**
*/
public publish(options: PublishRequest, callback: (err: Error | null, response?: PublishResponse) => void): void {
this._defaultRequest(PublishRequest, PublishResponse, options, callback);
}
/**
*
*/
public republish(options: RepublishRequest, callback: (err: Error | null, response?: RepublishResponse) => void): void {
this._defaultRequest(RepublishRequest, RepublishResponse, options, callback);
}
/**
*
*/
public deleteMonitoredItems(
options: DeleteMonitoredItemsRequestLike,
callback: (err: Error | null, response?: DeleteMonitoredItemsResponse) => void
): void {
this._defaultRequest(DeleteMonitoredItemsRequest, DeleteMonitoredItemsResponse, options, callback);
}
/**
*
*/
public setPublishingMode(publishingEnabled: boolean, subscriptionId: SubscriptionId): Promise<StatusCode>;
public setPublishingMode(publishingEnabled: boolean, subscriptionIds: SubscriptionId[]): Promise<StatusCode[]>;
public setPublishingMode(
publishingEnabled: boolean,
subscriptionId: SubscriptionId,
callback: (err: Error | null, statusCode?: StatusCode) => void
): void;
public setPublishingMode(
publishingEnabled: boolean,
subscriptionIds: SubscriptionId[],
callback: (err: Error | null, statusCodes?: StatusCode[]) => void
): void;
/**
* @internal
*/
public setPublishingMode(...args: any[]): any {
const publishingEnabled = args[0];
const isArray = Array.isArray(args[1]);
const subscriptionIds = isArray ? args[1] : [args[1]];
const callback = args[2];
assert(typeof callback === "function");
assert(publishingEnabled === true || publishingEnabled === false);
const options = new SetPublishingModeRequest({
publishingEnabled,
subscriptionIds
});
this._defaultRequest(
SetPublishingModeRequest,
SetPublishingModeResponse,
options,
(err: Error | null, response?: SetPublishingModeResponse) => {
/* c8 ignore next */
if (err) {
return callback(err);
}
/* c8 ignore next */
if (!response) {
return callback(new Error("Internal Error"));
}
response.results = response.results || /* c8 ignore next */ [];
callback(err, isArray ? response.results : response.results[0]);
}
);
}
/**
*
*/
public translateBrowsePath(browsePath: BrowsePath, callback: ResponseCallback<BrowsePathResult>): void;
public translateBr