diff --git a/source/nodes/OpcUa-Client2/shared/index.ts b/source/nodes/OpcUa-Client2/shared/index.ts index 937c94f..39d9d06 100644 --- a/source/nodes/OpcUa-Client2/shared/index.ts +++ b/source/nodes/OpcUa-Client2/shared/index.ts @@ -1,5 +1,4 @@ -import { AttributeIds } from "node-opcua"; -import { Node, NodeDef, NodeAPI } from "node-red"; +import { Node, NodeDef } from "node-red"; export type PossibleActions = | "read" diff --git a/source/nodes/OpcUa-Endpoint2/OpcUa-Endpoint2.html/editor.html b/source/nodes/OpcUa-Endpoint2/OpcUa-Endpoint2.html/editor.html index 2ab065f..19db4a0 100644 --- a/source/nodes/OpcUa-Endpoint2/OpcUa-Endpoint2.html/editor.html +++ b/source/nodes/OpcUa-Endpoint2/OpcUa-Endpoint2.html/editor.html @@ -1,99 +1,145 @@ + +
- - -
-
- - -
-
- - -
-
- Anonymous -
-
- use credentials + +
+
- user certificate + +
-
- - -
-
- - -
+
+ Security +
+ + +
+
+ + +
+
+
+ User Session Type + +
+ + +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- - + + + +
-
- - + +
+ +
+ Server certificate +
+ + +
+
+ +
+ Overwrite client certificate & private key +
+ + +
+
+ + +
+
+ + +
+
+ + +
+ Advanced transport settings +

only use those parameters if you know what you are doing !

+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
diff --git a/source/nodes/OpcUa-Endpoint2/OpcUa-Endpoint2.html/index.ts b/source/nodes/OpcUa-Endpoint2/OpcUa-Endpoint2.html/index.ts index d8611df..c69e772 100644 --- a/source/nodes/OpcUa-Endpoint2/OpcUa-Endpoint2.html/index.ts +++ b/source/nodes/OpcUa-Endpoint2/OpcUa-Endpoint2.html/index.ts @@ -28,25 +28,25 @@ RED.nodes.registerType("OpcUa-Endpoint2", { required: true, validate: isValidOPC_UA_URI, }, + // Security Mode + securityMode: { value: "None" }, + securityPolicy: { value: "None" }, - // TOdo use a combo here instead - none: { value: true }, - login: { value: false }, - usercert: { value: false }, + userIdentityType: { value: "Anonymous" }, + // to use when userIdentityType is Certificate userCertificate: { value: "" }, userPrivatekey: { value: "" }, + // username // These are fixed or can be read through endPoint certificate: { value: "n" }, localfile: { value: "" }, localkeyfile: { value: "" }, - securityMode: { value: "None" }, - securityPolicy: { value: "None" }, - folderName4PKI: { value: "" }, + // useTransport: { value: false, required: false }, maxChunkCount: { value: 1, required: false }, @@ -59,109 +59,93 @@ RED.nodes.registerType("OpcUa-Endpoint2", { password: { type: "password" }, }, label: function () { - return this.endpoint || "OPC UA Endpoint (2)"; + return this.name || this.endpoint || "invalid endpoint"; }, - oneditprepare: function (this: OpcUaEndpoint2NodeProperties) { - const node = this; - - const noneCheckbox = $("#node-config-input-none"); - const loginCheckbox = $("#node-config-input-login"); - const usercertCheckbox = $("#node-config-input-usercert"); - const authRow = $(".node-input-useAuth-row"); - const certRow = $(".node-input-userCert-row"); - noneCheckbox.prop("checked", node.none); - loginCheckbox.prop("checked", node.login); - usercertCheckbox.prop("checked", node.usercert); - - if (node.none === true) { - noneCheckbox.prop("checked", true); - // authRow.show(); - $("#node-input-user").val(""); - $("#node-input-password").val(""); - } else { - noneCheckbox.prop("checked", false); - // loginCheckbox.prop('checked', false); - // usercertCheckbox.prop('checked', false); - // authRow.hide(); - if (node.login === true) { - authRow.show(); - } else { - authRow.hide(); - } - if (node.usercert === true) { - certRow.show(); - } else { - certRow.hide(); - } + oneditsave() { + const _securityPolicy = $("#node-config-input-securityPolicy"); + const _securityMode = $("#node-config-input-securityMode"); + if (_securityPolicy.val() === "None") { + _securityMode.text("None"); } - - if (node.login === true) { - loginCheckbox.prop("checked", true); - authRow.show(); - } else { - loginCheckbox.prop("checked", false); - authRow.hide(); + }, + oneditprepare() { + const node = this; + // ----------------------------------------------------------------------- + // handle Security controls + // ----------------------------------------------------------------------- + { + const updateSecurityControl = (securityMode: string) => { + switch (securityMode) { + case null: + case undefined: + case "": + case "None": + $(".section-securityPolicy").hide(); + break; + case "Sign": + case "SignAndEncrypt": + $(".section-securityPolicy").show(); + break; + default: + console.log("invalid securityMode " + securityMode); + } + }; + updateSecurityControl(node.securityMode); + const _securityMode = $("#node-config-input-securityMode"); + _securityMode.on("change", () => { + const newSecurityMode = _securityMode.val(); + updateSecurityControl(newSecurityMode as string); + }); } - - if (node.usercert === true) { - usercertCheckbox.prop("checked", true); - certRow.show(); - $("#node-input-user").val(""); - $("#node-input-password").val(""); - } else { - usercertCheckbox.prop("checked", false); - certRow.hide(); + // ----------------------------------------------------------------------- + // handle the User Identity Controls + // ----------------------------------------------------------------------- + { + const authRow = $(".section-userIdentityType-UserName"); + const certRow = $(".section-userIdentityType-Certificate"); + + const updateIdentityControls = function (userIdentityType: string) { + switch (userIdentityType) { + case "Anonymous": + $("#node-input-user").val(""); + $("#node-input-password").val(""); + authRow.hide(); + certRow.hide(); + break; + case "UserName": + authRow.show(); + certRow.hide(); + break; + case "Certificate": + $("#node-input-user").val(""); + $("#node-input-password").val(""); + certRow.show(); + authRow.hide(); + break; + default: + console.log("invalid userIdentityType " + userIdentityType); + } + }; + updateIdentityControls(node.userIdentityType); + + const _userIdentityType = $("#node-config-input-userIdentityType"); + _userIdentityType.on("change", () => { + const newIdentityType = _userIdentityType.val(); + updateIdentityControls(newIdentityType as string); + }); + + // ----------------------------------------------------------------------- + // handle advanced controls + // ----------------------------------------------------------------------- + $("#toggleAdvancedSection").on("change", function (this) { + if ($(this).prop("checked")) { + // If the checkbox is checked, show the div + $("#section-Advanced").show(); + } else { + // Otherwise, hide it + $("#section-Advanced").hide(); + } + }); } - noneCheckbox.on("change", function (this) { - if ($(this).is(":checked")) { - console.log("Changed Anonymous: TRUE"); - node.none = true; - noneCheckbox.prop("checked", true); - loginCheckbox.prop("checked", false); - usercertCheckbox.prop("checked", false); - $("#node-input-user").val(""); - $("#node-input-password").val(""); - authRow.hide(); - certRow.hide(); - } else { - console.log("Changed Anonymous: FALSE"); - node.none = false; - // $('#node-input-user').val(''); - // $('#node-input-password').val(''); - } - }); - - loginCheckbox.on("change", function () { - if ($(this).is(":checked")) { - node.login = true; - usercertCheckbox.prop("checked", false); - noneCheckbox.prop("checked", false); - authRow.show(); - certRow.hide(); - } else { - node.login = false; - authRow.hide(); - certRow.hide(); - // DO NOT CLEAR - // $('#node-input-user').val(''); - // $('#node-input-password').val(''); - } - }); - - usercertCheckbox.on("change", function () { - if ($(this).is(":checked")) { - node.usercert = true; - loginCheckbox.prop("checked", false); - noneCheckbox.prop("checked", false); - certRow.show(); - authRow.hide(); - } else { - node.usercert = false; - certRow.hide(); - authRow.hide(); - $("#node-input-usercertificate").val(""); - $("#node-input-userprivatekey").val(""); - } - }); }, }); diff --git a/source/nodes/OpcUa-Endpoint2/OpcUa-Endpoint2.ts b/source/nodes/OpcUa-Endpoint2/OpcUa-Endpoint2.ts index fd29265..86bc0b2 100644 --- a/source/nodes/OpcUa-Endpoint2/OpcUa-Endpoint2.ts +++ b/source/nodes/OpcUa-Endpoint2/OpcUa-Endpoint2.ts @@ -8,11 +8,11 @@ const nodeInit: NodeInitializer = (RED): void => { RED.nodes.registerType("OpcUa-Endpoint2", function (this: OpcUaEndpoint2Node, config: OpcUaEndpoint2NodeDef) { RED.nodes.createNode(this, config); this.endpoint = config.endpoint; + /// this.securityPolicy = config.securityPolicy; this.securityMode = config.securityMode; - this.login = config.login; - this.none = config.none; - this.usercert = config.usercert; + /// + this.userIdentityType = config.userIdentityType; this.userCertificate = config.userCertificate; this.userPrivatekey = config.userPrivatekey; diff --git a/source/nodes/OpcUa-Endpoint2/shared/index.ts b/source/nodes/OpcUa-Endpoint2/shared/index.ts index 773d337..6a7fd0a 100644 --- a/source/nodes/OpcUa-Endpoint2/shared/index.ts +++ b/source/nodes/OpcUa-Endpoint2/shared/index.ts @@ -1,18 +1,36 @@ import { Node, NodeDef } from "node-red"; +import { UserTokenType} from "node-opcua"; + +export type UserTokenTypeStr = "UserName" | "Certificate" | "Anonymous"; +export type SecurityModeStr = "None" | "Sign" | "SignEncrypt"; export interface OpcUaEndpoint2Attributes { + /** + * OPC UA Endpoint URL in the form `opc.tcp://[hostname]:[port]/[path]` + */ endpoint: string; + // Security + /** + * the Security mode: + * + * could be "None" , "Sign" or "SignEncrypt" + */ + securityMode: SecurityModeStr; + /** + * the security policy. + * + * the securityPolicy must be None when securityMode is None + */ securityPolicy: string; - securityMode: string; - // to do change to Enum - login: boolean; - none: boolean; - usercert: Boolean; + // User Identity + userIdentityType: UserTokenTypeStr; + // User Identity: X509 Certificate userCertificate: string; userPrivatekey: string; + // certificate: string; localfile: string; localkeyfile: string; @@ -24,7 +42,6 @@ export interface OpcUaEndpoint2Attributes { maxChunkCount: number; maxMessageSize: number; receiveBufferSize: number; - sendBufferSize: number; } export interface OpcUaEndpoint2NodeDef extends OpcUaEndpoint2Attributes, NodeDef {} diff --git a/source/nodes/tools/connection_tools.ts b/source/nodes/tools/connection_tools.ts index 39141a9..3f06a71 100644 --- a/source/nodes/tools/connection_tools.ts +++ b/source/nodes/tools/connection_tools.ts @@ -19,7 +19,7 @@ limitations under the License. import { Node } from "node-red"; import { ClientSession, OPCUAClient } from "node-opcua-client"; import { OpcUaEndpoint2Node } from "../../nodes/OpcUa-Endpoint2/shared"; -import { ClientSubscription, ClientSubscriptionOptions } from "node-opcua"; +import { ClientSubscription, ClientSubscriptionOptions, MessageSecurityMode, SecurityPolicy, coerceSecurityPolicy } from "node-opcua"; interface Connection { client: OPCUAClient; @@ -57,6 +57,11 @@ export async function get_opcua_endpoint(endpointNode: OpcUaEndpoint2Node): Prom pendingConnection.set(endpointId, []); + const securityMode = MessageSecurityMode[(endpointNode.securityMode as any) || "None"] || MessageSecurityMode.None; + const securityPolicy = coerceSecurityPolicy(endpointNode.securityPolicy); + endpointNode.warn("securityMode = " + securityMode); + endpointNode.warn("securityPolicy = " + securityPolicy); + const client = OPCUAClient.create({ endpointMustExist: false, connectionStrategy: { @@ -64,6 +69,8 @@ export async function get_opcua_endpoint(endpointNode: OpcUaEndpoint2Node): Prom initialDelay: 1000, maxDelay: 10000, }, + securityMode, + securityPolicy, }); client.on("backoff", (attempt, delay) => { endpointNode.warn("backoff " + attempt + " " + delay); diff --git a/source/tests/OpcUaClient2/OpcUaClient2.spec.ts b/source/tests/OpcUaClient2/OpcUaClient2.spec.ts index 2314a1d..1a663ea 100644 --- a/source/tests/OpcUaClient2/OpcUaClient2.spec.ts +++ b/source/tests/OpcUaClient2/OpcUaClient2.spec.ts @@ -1,4 +1,6 @@ import helper, { TestFlowsItem } from "node-red-node-test-helper"; +import { Node, NodeDef } from "node-red"; + // import { describe, it, beforeEach, afterEach } from "mocha"; import should from "should"; import sinon from "sinon"; @@ -6,7 +8,7 @@ import OpcUaClient2 from "../../nodes/OpcUa-Client2/OpcUa-Client2"; import OpcUaEndpoint2 from "../../nodes/OpcUa-Endpoint2/OpcUa-Endpoint2"; import { OpcUaClient2Node, OpcUaClient2NodeDef } from "../../nodes/OpcUa-Client2/shared"; -import { OpcUaEndpoint2Node, OpcUaEndpoint2NodeDef } from "../../nodes/OpcUa-Endpoint2/shared"; +import { OpcUaEndpoint2Attributes, OpcUaEndpoint2Node, OpcUaEndpoint2NodeDef } from "../../nodes/OpcUa-Endpoint2/shared"; import { DataValue, OPCUAServer, StatusCodes, s } from "node-opcua"; console.log("don't forget to run npm install ~/projects/node-red/packages/node_modules/node-red --no-save"); @@ -47,7 +49,7 @@ describe("OpcUa-Client2 node", function () { type: "OpcUa-Endpoint2", name: "endpoint1", endpoint: "opc.tcp://127.0.0.1:62544", - login: false, + userIdentityType: "Anonymous", }, { id: "n1", type: "OpcUa-Client2", name: "node1", endpoint: "e1" }, ]; @@ -86,21 +88,234 @@ describe("OpcUa-Client2 node", function () { await server?.shutdown(); }); - beforeEach(async () => { - flows = [ + describe("with standard flows", () => { + beforeEach(async () => { + flows = [ + { + id: "e1", + type: "OpcUa-Endpoint2", + name: "endpoint1", + endpoint: "opc.tcp://127.0.0.1:62544/", + }, + { + id: "e2", + type: "OpcUa-Endpoint2", + name: "unreachable endpoint", + endpoint: "opc.tcp://opcua-server.unreachable.com:62544", + }, + { + id: "n1", + type: "OpcUa-Client2", + name: "node1", + endpoint: "e1", + action: "read", + wires: [["h1"]], + }, + { + id: "n2", + type: "OpcUa-Client2", + name: "OPCUA Read on a non existing endpoint", + endpoint: "e2", + action: "read", + wires: [["h1"]], + }, + { + id: "n3", + type: "OpcUa-Client2", + name: "node3", // an other valid opcua client node for a read operation + endpoint: "e1", + action: "read", + wires: [["h1"]], + }, + { id: "h1", type: "helper" }, + { + id: "se1", + type: "OpcUa-Client2", + name: "get_server_endpoint_1", + endpoint: "e1", + action: "get_server_endpoints", + wires: [["h1"]], + }, + { + id: "n4", + type: "OpcUa-Client2", + name: "node4", + endpoint: "e1", + action: "read", + attributeId: "All", + wires: [["h1"]], + }, + { + id: "n5", + type: "OpcUa-Client2", + name: "All Attribute Read", + endpoint: "e1", + action: "read", + attributeId: "All", + wires: [["h1"]], + }, + ]; + await helper.load([OpcUaEndpoint2, OpcUaClient2], flows); + }); + + it("server_endpoints-1: should get OPCUA Server endpoints", (done) => { + const helperNode = helper.getNode("h1"); + const opcuaNode = helper.getNode("se1"); + helperNode.on("input", (msg: any) => { + try { + // console.log(msg); + Array.isArray(msg.payload).should.equal(true); + (msg.payload.length as number).should.be.greaterThanOrEqual(1); + (msg.payload[0] as {}).should.have.property("endpointUrl"); + msg.payload[0].should.have.property("securityMode"); + msg.payload[0].should.have.property("securityPolicyUri"); + msg.payload[0].should.have.property("securityLevel"); + msg.payload[0].should.have.property("serverCertificate"); + done(); + } catch (err) { + done(err); + } + }); + opcuaNode.receive({ topic: "doesn't matter" }); + }); + + it("read-1: should read a simple OPCUA variable (CurrentDate: = i=2258)", (done) => { + const helperNode = helper.getNode("h1"); + const opcuaNode = helper.getNode("n1"); + helperNode.on("input", (msg: any) => { + console.log(msg); + try { + msg.should.have.property("$dataValue"); + msg.should.have.property("payload"); + msg.$dataValue.should.be.instanceOf(DataValue); + msg.$dataValue.statusCode.toString().should.eql(StatusCodes.Good.toString()); + done(); + } catch (err) { + done(err); + } + }); + opcuaNode.receive({ topic: "ns=0;i=2258" }); + }); + + it("read-1-b: should read a simple OPCUA variable (CurrentDate: = i=2258 - with attributeId=AttributeIds.BrowseName set to node)", (done) => { + const helperNode = helper.getNode("h1"); + const opcuaNode = helper.getNode("n1"); + helperNode.on("input", (msg: any) => { + console.log(msg); + try { + msg.should.have.property("$dataValue"); + msg.should.have.property("payload"); + msg.$dataValue.should.be.instanceOf(DataValue); + msg.$dataValue.statusCode.toString().should.eql(StatusCodes.Good.toString()); + done(); + } catch (err) { + done(err); + } + }); + opcuaNode.receive({ topic: "ns=0;i=2258" }); + }); + + it("read-1-c: reading a simple OPCUA variable from a node-Id that does not exist", (done) => { + const helperNode = helper.getNode("h1"); + const opcuaNode = helper.getNode("n1"); + helperNode.on("input", (msg: any) => { + doDebug && console.log(msg); + try { + msg.should.have.property("$dataValue"); + msg.should.have.property("payload"); + msg.$dataValue.should.be.instanceOf(DataValue); + should(msg.payload).equals(null); + msg.$dataValue.statusCode.toString().should.eql(StatusCodes.BadNodeIdUnknown.toString()); + done(); + } catch (err) { + done(err); + } + }); + opcuaNode.receive({ topic: "ns=25;s=WhereIsThisNode" }); + }); + + it("read-2: should raise a error if the endpoint is not reachable", (done) => { + const helperNode = helper.getNode("h1"); + const opcuaNode = helper.getNode("n2") as OpcUaClient2Node; + const opcuaEndpointNode = helper.getNode("e2") as OpcUaEndpoint2Node; + + helperNode.on("input", (msg: unknown) => { + doDebug && console.log(msg); + // console.log("error count ", (opcuaNode.error as any).callCount); + done(); + }); + opcuaNode.receive({ topic: "ns=0;i=2258" }); + }); + + it("read-3: should raise a error if the server is down", (done) => { + const helperNode = helper.getNode("h1"); + const opcuaNode = helper.getNode("n1") as OpcUaClient2Node; + const opcuaEndpointNode = helper.getNode("e1") as OpcUaEndpoint2Node; + + helperNode.on("input", (msg: unknown) => { + doDebug && console.log(msg); + // console.log("error count ", (opcuaNode.error as any).callCount); + done(); + }); + + shutdownServer().then(() => { + opcuaNode.receive({ topic: "ns=0;i=2258" }); + }); + }); + + it("read-4: parallel read - multiple node reading some variables on the same server at the same time", (done) => { + const helperNode = helper.getNode("h1"); + const opcuaNode1 = helper.getNode("n1") as OpcUaClient2Node; + const opcuaNode2 = helper.getNode("n3") as OpcUaClient2Node; + const opcuaNode3 = helper.getNode("n4") as OpcUaClient2Node; + + let counter = 0; + helperNode.on("input", (msg: unknown) => { + counter++; + doDebug && console.log(msg); + // console.log("error count ", (opcuaNode.error as any).callCount); + if (counter == 3) { + done(); + } + }); + + opcuaNode1.receive({ topic: "ns=0;i=2258" }); + opcuaNode2.receive({ topic: "ns=0;i=2258" }); + opcuaNode3.receive({ topic: "ns=0;i=2258" }); + }); + + it("read-5: all attributes read", (done) => { + const helperNode = helper.getNode("h1"); + const opcuaNode = helper.getNode("n5") as OpcUaClient2Node; + helperNode.on("input", (msg: unknown) => { + console.log(msg); + done(); + }); + opcuaNode.receive({ topic: "ns=0;i=2258" }); + }); + }); + + const waitFirstInput = async (opcuaNode: Node, helperNode: any) => { + return await new Promise((resolve, reject) => { + helperNode.on("input", (msg: unknown) => { + doDebug && console.log(msg); + resolve(msg); + }); + opcuaNode.receive({ topic: "ns=0;i=2258" }); + }); + }; + it("endpoint-security-1: Sign & Basic256Sha256 with anonymous Identity", async () => { + const flows = [ { id: "e1", type: "OpcUa-Endpoint2", name: "endpoint1", endpoint: "opc.tcp://127.0.0.1:62544/", - login: false, - }, - { - id: "e2", - type: "OpcUa-Endpoint2", - name: "unreachable endpoint", - endpoint: "opc.tcp://opcua-server.unreachable.com:62544", - login: false, + + securityMode: "SignAndEncrypt", + securityPolicy: "Basic256Sha256", + + userIdentityType: "Anonymous", }, { id: "n1", @@ -110,185 +325,39 @@ describe("OpcUa-Client2 node", function () { action: "read", wires: [["h1"]], }, - { - id: "n2", - type: "OpcUa-Client2", - name: "OPCUA Read on a non existing endpoint", - endpoint: "e2", - action: "read", - wires: [["h1"]], - }, - { - id: "n3", - type: "OpcUa-Client2", - name: "node3", // an other valid opcua client node for a read operation - endpoint: "e1", - action: "read", - wires: [["h1"]], - }, { id: "h1", type: "helper" }, + ]; + await helper.load([OpcUaEndpoint2, OpcUaClient2], flows); + const helperNode = helper.getNode("h1"); + const opcuaNode = helper.getNode("n1") as OpcUaClient2Node; + const msg = await waitFirstInput(opcuaNode, helperNode); + console.log(msg); + }); + it("endpoint-security-2: Username Password, with valid credentials", async () => { + const flows = [ { - id: "se1", - type: "OpcUa-Client2", - name: "get_server_endpoint_1", - endpoint: "e1", - action: "get_server_endpoints", - wires: [["h1"]], - }, - { - id: "n4", - type: "OpcUa-Client2", - name: "node4", - endpoint: "e1", - action: "read", - attributeId: "All", - wires: [["h1"]], + id: "e1", + type: "OpcUa-Endpoint2", + name: "endpoint1", + endpoint: "opc.tcp://127.0.0.1:62544/", + userIdentityType: "UserName", + user: "user1", + password: "password1", }, { - id: "n5", + id: "n1", type: "OpcUa-Client2", - name: "All Attribute Read", + name: "node1", endpoint: "e1", action: "read", - attributeId: "All", wires: [["h1"]], }, + { id: "h1", type: "helper" }, ]; await helper.load([OpcUaEndpoint2, OpcUaClient2], flows); - }); - - it("server_endpoints-1: should get OPCUA Server endpoints", (done) => { - const helperNode = helper.getNode("h1"); - const opcuaNode = helper.getNode("se1"); - helperNode.on("input", (msg: any) => { - try { - // console.log(msg); - Array.isArray(msg.payload).should.equal(true); - (msg.payload.length as number).should.be.greaterThanOrEqual(1); - (msg.payload[0] as {}).should.have.property("endpointUrl"); - msg.payload[0].should.have.property("securityMode"); - msg.payload[0].should.have.property("securityPolicyUri"); - msg.payload[0].should.have.property("securityLevel"); - msg.payload[0].should.have.property("serverCertificate"); - done(); - } catch (err) { - done(err); - } - }); - opcuaNode.receive({ topic: "doesn't matter" }); - }); - - it("read-1: should read a simple OPCUA variable (CurrentDate: = i=2258)", (done) => { - const helperNode = helper.getNode("h1"); - const opcuaNode = helper.getNode("n1"); - helperNode.on("input", (msg: any) => { - console.log(msg); - try { - msg.should.have.property("$dataValue"); - msg.should.have.property("payload"); - msg.$dataValue.should.be.instanceOf(DataValue); - msg.$dataValue.statusCode.toString().should.eql(StatusCodes.Good.toString()); - done(); - } catch (err) { - done(err); - } - }); - opcuaNode.receive({ topic: "ns=0;i=2258" }); - }); - it("read-1-b: should read a simple OPCUA variable (CurrentDate: = i=2258 - with attributeId=AttributeIds.BrowseName set to node)", (done) => { - const helperNode = helper.getNode("h1"); - const opcuaNode = helper.getNode("n1"); - helperNode.on("input", (msg: any) => { - console.log(msg); - try { - msg.should.have.property("$dataValue"); - msg.should.have.property("payload"); - msg.$dataValue.should.be.instanceOf(DataValue); - msg.$dataValue.statusCode.toString().should.eql(StatusCodes.Good.toString()); - done(); - } catch (err) { - done(err); - } - }); - opcuaNode.receive({ topic: "ns=0;i=2258" }); - }); - - it("read-1-c: reading a simple OPCUA variable from a node-Id that does not exist", (done) => { - const helperNode = helper.getNode("h1"); - const opcuaNode = helper.getNode("n1"); - helperNode.on("input", (msg: any) => { - doDebug && console.log(msg); - try { - msg.should.have.property("$dataValue"); - msg.should.have.property("payload"); - msg.$dataValue.should.be.instanceOf(DataValue); - should(msg.payload).equals(null); - msg.$dataValue.statusCode.toString().should.eql(StatusCodes.BadNodeIdUnknown.toString()); - done(); - } catch (err) { - done(err); - } - }); - opcuaNode.receive({ topic: "ns=25;s=WhereIsThisNode" }); - }); - - it("read-2: should raise a error if the endpoint is not reachable", (done) => { - const helperNode = helper.getNode("h1"); - const opcuaNode = helper.getNode("n2") as OpcUaClient2Node; - const opcuaEndpointNode = helper.getNode("e2") as OpcUaEndpoint2Node; - - helperNode.on("input", (msg: unknown) => { - doDebug && console.log(msg); - // console.log("error count ", (opcuaNode.error as any).callCount); - done(); - }); - opcuaNode.receive({ topic: "ns=0;i=2258" }); - }); - - it("read-3: should raise a error if the server is down", (done) => { const helperNode = helper.getNode("h1"); const opcuaNode = helper.getNode("n1") as OpcUaClient2Node; - const opcuaEndpointNode = helper.getNode("e1") as OpcUaEndpoint2Node; - - helperNode.on("input", (msg: unknown) => { - doDebug && console.log(msg); - // console.log("error count ", (opcuaNode.error as any).callCount); - done(); - }); - - shutdownServer().then(() => { - opcuaNode.receive({ topic: "ns=0;i=2258" }); - }); - }); - - it("read-4: parallel read - multiple node reading some variables on the same server at the same time", (done) => { - const helperNode = helper.getNode("h1"); - const opcuaNode1 = helper.getNode("n1") as OpcUaClient2Node; - const opcuaNode2 = helper.getNode("n3") as OpcUaClient2Node; - const opcuaNode3 = helper.getNode("n4") as OpcUaClient2Node; - - let counter = 0; - helperNode.on("input", (msg: unknown) => { - counter++; - doDebug && console.log(msg); - // console.log("error count ", (opcuaNode.error as any).callCount); - if (counter == 3) { - done(); - } - }); - - opcuaNode1.receive({ topic: "ns=0;i=2258" }); - opcuaNode2.receive({ topic: "ns=0;i=2258" }); - opcuaNode3.receive({ topic: "ns=0;i=2258" }); - }); - it("read-5: all attributes read", (done) => { - const helperNode = helper.getNode("h1"); - const opcuaNode = helper.getNode("n5") as OpcUaClient2Node; - helperNode.on("input", (msg: unknown) => { - console.log(msg); - done(); - }); - opcuaNode.receive({ topic: "ns=0;i=2258" }); + const msg = await waitFirstInput(opcuaNode, helperNode); }); }); });