Example #1
0
async function main() {

    const optionsInitial = {
        endpoint_must_exist: false,
        keepSessionAlive: true,

        connectionStrategy: {
            initialDelay: 2000,
            maxDelay: 10 * 1000,
            maxRetry: 10
        }
    };

    client = OPCUAClient.create(optionsInitial);

    client.on("backoff", (retry: number, delay: number) => {
        console.log(chalk.bgWhite.yellow("backoff  attempt #"), retry, " retrying in ", delay / 1000.0, " seconds");
    });

    console.log(" connecting to ", chalk.cyan.bold(endpointUrl));
    console.log("    strategy", client.connectionStrategy);

    await client.connect(endpointUrl);

    const endpoints = await client.getEndpoints();

    if (argv.debug) {
        fs.writeFileSync("tmp/endpoints.log", JSON.stringify(endpoints, null, " "));
        console.log(treeify.asTree(endpoints, true));
    }

    const table = new Table();

    let serverCertificate: Certificate;

    let i = 0;
    for (const endpoint of endpoints) {
        table.cell("endpoint", endpoint.endpointUrl + "");
        table.cell("Application URI", endpoint.server.applicationUri);
        table.cell("Product URI", endpoint.server.productUri);
        table.cell("Application Name", endpoint.server.applicationName.text);
        table.cell("Security Mode", endpoint.securityMode.toString());
        table.cell("securityPolicyUri", endpoint.securityPolicyUri);
        table.cell("Type", ApplicationType[endpoint.server.applicationType]);
        table.cell("certificate", "..." /*endpoint.serverCertificate*/);
        endpoint.server.discoveryUrls = endpoint.server.discoveryUrls || [];
        table.cell("discoveryUrls", endpoint.server.discoveryUrls.join(" - "));

        serverCertificate = endpoint.serverCertificate;

        const certificate_filename = path.join(__dirname, "../certificates/PKI/server_certificate" + i + ".pem");

        if (serverCertificate) {
            fs.writeFile(certificate_filename, toPem(serverCertificate, "CERTIFICATE"), () => {/**/
            });
        }
        table.newRow();
        i++;
    }
    console.log(table.toString());

    for (const endpoint of endpoints) {
        console.log("Identify Token for : Security Mode=", endpoint.securityMode.toString(), " Policy=", endpoint.securityPolicyUri);
        const table2 = new Table();
        for (const token of endpoint.userIdentityTokens!) {
            table2.cell("policyId", token.policyId);
            table2.cell("tokenType", token.tokenType.toString());
            table2.cell("issuedTokenType", token.issuedTokenType);
            table2.cell("issuerEndpointUrl", token.issuerEndpointUrl);
            table2.cell("securityPolicyUri", token.securityPolicyUri);
            table2.newRow();
        }
        console.log(table2.toString());
    }
    await client.disconnect();

    // reconnect using the correct end point URL now
    console.log(chalk.cyan("Server Certificate :"));
    console.log(chalk.yellow(hexDump(serverCertificate!)));

    const options = {
        securityMode,
        securityPolicy,
        serverCertificate,

        defaultSecureTokenLifetime: 40000,

        endpoint_must_exist: false,

        connectionStrategy: {
            initialDelay: 2000,
            maxDelay: 10 * 1000,
            maxRetry: 10
        }
    };
    console.log("Options = ", options.securityMode.toString(), options.securityPolicy.toString());

    client = OPCUAClient.create(options);

    console.log(" reconnecting to ", chalk.cyan.bold(endpointUrl));
    await client.connect(endpointUrl);

    let userIdentity: any; // anonymous
    if (argv.userName && argv.password) {

        userIdentity = {
            password: argv.password,
            userName: argv.userName
        };

    }

    the_session = await client.createSession(userIdentity);
    client.on("connection_reestablished", () => {
        console.log(chalk.bgWhite.red(" !!!!!!!!!!!!!!!!!!!!!!!!  CONNECTION RE-ESTABLISHED !!!!!!!!!!!!!!!!!!!"));
    });
    console.log(chalk.yellow(" session created"));
    console.log(" sessionId : ", the_session.sessionId.toString());

    client.on("backoff", (retry: number, delay: number) => {
        console.log(chalk.bgWhite.yellow("backoff  attempt #"), retry, " retrying in ", delay / 1000.0, " seconds");
    });
    client.on("start_reconnection", () => {
        console.log(chalk.bgWhite.red(" !!!!!!!!!!!!!!!!!!!!!!!!  Starting Reconnection !!!!!!!!!!!!!!!!!!!"));
    });

    // -----------------------------------------------------------------------------------------------------------
    //   NAMESPACE
    //   display namespace array
    // -----------------------------------------------------------------------------------------------------------
    const server_NamespaceArray_Id = makeNodeId(VariableIds.Server_NamespaceArray); // ns=0;i=2006

    const dataValue = await the_session.readVariableValue(server_NamespaceArray_Id);

    console.log(" --- NAMESPACE ARRAY ---");
    const namespaceArray = dataValue.value.value;
    for (const namespace of namespaceArray) {
        console.log(" Namespace ", namespace.index, "  : ", namespace);
    }
    console.log(" -----------------------");

    // -----------------------------------------------------------------------------------------------------------
    //   enumerate all EVENT TYPES
    // -----------------------------------------------------------------------------------------------------------
    const result = getAllEventTypes(the_session);
    console.log(chalk.cyan("---------------------------------------------------- All Event Types "));
    console.log(treeify.asTree(result, true));
    console.log(" -----------------------");

    // -----------------------------------------------------------------------------------------------------------
    //   Node Crawling
    // -----------------------------------------------------------------------------------------------------------
    let t1: number;
    let t2: number;

    function print_stat() {
        t2 = Date.now();
        const str = util.format("R= %d W= %d T=%d t= %d",
          client.bytesRead, client.bytesWritten, client.transactionsPerformed, (t2 - t1));
        console.log(chalk.yellow.bold(str));
    }

    if (doCrawling) {
        assert(_.isObject(the_session));
        const crawler = new NodeCrawler(the_session);

        let t5 = Date.now();
        client.on("send_request", () => {
            t1 = Date.now();
        });

        client.on("receive_response", print_stat);

        t5 = Date.now();
        // xx crawler.on("browsed", function (element) {
        // xx     console.log("->",(new Date()).getTime()-t,element.browseName.name,element.nodeId.toString());
        // xx });

        const nodeId = "ObjectsFolder";
        console.log("now crawling object folder ...please wait...");

        const obj = await crawler.read(nodeId);
        console.log(" Time        = ", (new Date()).getTime() - t5);
        console.log(" read        = ", crawler.readCounter);
        console.log(" browse      = ", crawler.browseCounter);
        console.log(" browseNext  = ", crawler.browseNextCounter);
        console.log(" transaction = ", crawler.transactionCounter);
        if (false) {
            // todo : treeify.asTree performance is *very* slow on large object, replace with better implementation
            // xx console.log(treeify.asTree(obj, true));
            treeify.asLines(obj, true, true, (line: string) => {
                console.log(line);
            });
        }
    }
    client.removeListener("receive_response", print_stat);

    // -----------------------------------------------------------------------------------------------------------------
    // enumerate all Condition Types exposed by the server
    // -----------------------------------------------------------------------------------------------------------------

    console.log("--------------------------------------------------------------- Enumerate all Condition Types exposed by the server");
    const conditionTree = await enumerateAllConditionTypes(the_session);
    console.log(treeify.asTree(conditionTree));
    console.log(" -----------------------------------------------------------------------------------------------------------------");

    // -----------------------------------------------------------------------------------------------------------------
    // enumerate all objects that have an Alarm & Condition instances
    // -----------------------------------------------------------------------------------------------------------------

    const alarms = await enumerateAllAlarmAndConditionInstances(the_session);

    console.log(" -------------------------------------------------------------- Alarms & Conditions ------------------------");
    for (const alarm of alarms) {
        console.log(
          "parent = ",
          chalk.cyan(w(alarm.parent.toString(), 30)),
          chalk.green.bold(w(alarm.typeDefinitionName, 30)),
          "alarmName = ",
          chalk.cyan(w(alarm.browseName.toString(), 30)),
          chalk.yellow(w(alarm.alarmNodeId.toString(), 40))
        );
    }
    console.log(" -----------------------------------------------------------------------------------------------------------------");

    // -----------------------------------------------------------------------------------------------------------------
    // Testing if server implements QueryFirst
    // -----------------------------------------------------------------------------------------------------------------
    try {
        console.log(" ----------------------------------------------------------  Testing QueryFirst");
        const queryFirstRequest: QueryFirstRequestOptions =  {
            view: {
                viewId: NodeId.nullNodeId
            },

            nodeTypes: [
                {
                    typeDefinitionNode: makeExpandedNodeId("i=58"),

                    includeSubTypes: true,

                    dataToReturn: [{

                        attributeId: AttributeIds.AccessLevel,
                        relativePath: undefined
                    }]
                }
            ]};

        const queryFirstResult = await the_session.queryFirst(queryFirstRequest);
        console.log(" -----------------------------------------------------------------------------------------------------------------");
    } catch (err) {
        console.log(" Server is not supporting queryFirst err=",err.message);
    }
    // create Read
    if (doHistory) {

        console.log(" ---------------------------------------------------------- History Read------------------------");
        const now = Date.now();
        const start = now - 1000; // read 1 seconds of history
        const historicalReadResult = await the_session.readHistoryValue({
            nodeId: monitored_node
        }, new Date(start), new Date(now));
        console.log(" -----------------------------------------------------------------------------------------------------------------");
    }

    // ----------------------------------------------------------------------------------
    // create subscription
    // ----------------------------------------------------------------------------------
    console.log(" ----------------------------------------------------------  Create Subscription ");
    const parameters = {
        maxNotificationsPerPublish: 10,
        priority: 10,
        publishingEnabled: true,
        requestedLifetimeCount: 1000,
        requestedMaxKeepAliveCount: 12,
        requestedPublishingInterval: 2000
    };

    the_subscription = await the_session.createSubscription2(parameters);

    let t = getTick();

    console.log("started subscription :", the_subscription!.subscriptionId);
    console.log(" revised parameters ");
    console.log("  revised maxKeepAliveCount  ", the_subscription!.maxKeepAliveCount, " ( requested ", parameters.requestedMaxKeepAliveCount + ")");
    console.log("  revised lifetimeCount      ", the_subscription!.lifetimeCount, " ( requested ", parameters.requestedLifetimeCount + ")");
    console.log("  revised publishingInterval ", the_subscription!.publishingInterval, " ( requested ", parameters.requestedPublishingInterval + ")");

    the_subscription.on("internal_error", (err: Error) => {
        console.log(" received internal error", err.message);
    }).on("keepalive", () => {
        const t4 = getTick();
        const span = t4 - t;
        t = t4;
        console.log("keepalive ", span / 1000, "sec",
          " pending request on server = ", (the_subscription as any).getPublishEngine().nbPendingPublishRequests);

    }).on("terminated", () => { /* */
    });

    try  {
        const results1 = await the_session.getMonitoredItems(the_subscription.subscriptionId);
        console.log("MonitoredItems clientHandles", results1.clientHandles);
        console.log("MonitoredItems serverHandles", results1.serverHandles);
    }  catch(err) {
        console.log("Server doesn't seems to implement getMonitoredItems method ", err.message);
    }
    // get_monitored_item

    // monitor_a_variable_node_value
    console.log("Monitoring monitor_a_variable_node_value");

    // ---------------------------------------------------------------
    //  monitor a variable node value
    // ---------------------------------------------------------------
    console.log(" Monitoring node ", monitored_node.toString());
    const monitoredItem = ClientMonitoredItem.create(
      the_subscription,
      {
          attributeId: AttributeIds.Value,
          nodeId: monitored_node
      },
      {
          discardOldest: true,
          queueSize: 10000,
          samplingInterval: 1000
          // xx filter:  { parameterTypeId: "ns=0;i=0",  encodingMask: 0 },
      }
    );
    monitoredItem.on("initialized", () => {
        console.log("monitoredItem initialized");
    });
    monitoredItem.on("changed", (dataValue1: DataValue) => {
        console.log(monitoredItem.itemToMonitor.nodeId.toString(), " value has changed to " + dataValue1.value.toString());
    });
    monitoredItem.on("err", (err_message: string) => {
        console.log(monitoredItem.itemToMonitor.nodeId.toString(), chalk.red(" ERROR"), err_message);
    });

    const results = await the_session.getMonitoredItems(the_subscription.subscriptionId);
    console.log("MonitoredItems clientHandles", results.clientHandles);
    console.log("MonitoredItems serverHandles", results.serverHandles);


    console.log("Monitoring monitor_the_object_events");

    // ---------------------------------------------------------------
    //  monitor the object events
    // ---------------------------------------------------------------

    const baseEventTypeId = "i=2041"; // BaseEventType;
    const serverObjectId = "i=2253";

    const fields = [
        "EventId",
        "EventType",
        "SourceNode",
        "SourceName",
        "Time",
        "ReceiveTime",
        "Message",
        "Severity",

        // ConditionType
        "ConditionClassId",
        "ConditionClassName",
        "ConditionName",
        "BranchId",
        "Retain",
        "EnabledState",
        "Quality",
        "LastSeverity",
        "Comment",
        "ClientUserId",

        // AcknowledgeConditionType
        "AckedState",
        "ConfirmedState",

        // AlarmConditionType
        "ActiveState",
        "InputNode",
        "SuppressedState",

        "HighLimit",
        "LowLimit",
        "HighHighLimit",
        "LowLowLimit",

        "Value"
    ];

    const eventFilter = constructEventFilter(fields, [
        resolveNodeId("ConditionType")
    ]);

    const event_monitoringItem = ClientMonitoredItem.create(
      the_subscription,
      {
          attributeId: AttributeIds.EventNotifier,
          nodeId: serverObjectId
      },
      {
          discardOldest: true,
          filter: eventFilter,
          queueSize: 100000
      }
    );

    event_monitoringItem.on("initialized", () => {
        console.log("event_monitoringItem initialized");
    });

    event_monitoringItem.on("changed", (eventFields: Variant[]) => {
        dumpEvent(the_session, fields, eventFields);
    });
    event_monitoringItem.on("err", (err_message: string) => {
        console.log(chalk.red("event_monitoringItem ", baseEventTypeId, " ERROR"), err_message);
    });

    console.log("--------------------------------------------- Monitoring alarms");
    const alarmNodeId = coerceNodeId("ns=2;s=1:Colours/EastTank?Green");
    await monitorAlarm(the_subscription, alarmNodeId);

    console.log("Starting timer ", timeout);
    if (timeout > 0) {

        // simulate a connection break at t =timeout/2
        // new Promise((resolve) => {
        setTimeout(() => {

            console.log(chalk.red("  -------------------------------------------------------------------- "));
            console.log(chalk.red("  --                               SIMULATE CONNECTION BREAK        -- "));
            console.log(chalk.red("  -------------------------------------------------------------------- "));
            const socket = (client as any)._secureChannel._transport._socket;
            socket.end();
            socket.emit("error", new Error("ECONNRESET"));
        }, timeout / 2.0);
        // });

        await new Promise((resolve) => {
            setTimeout(async () => {
                console.log("time out => shutting down ");
                if (!the_subscription) {
                    return resolve();
                }
                if (the_subscription) {
                    const s = the_subscription;
                    the_subscription = null;
                    await s.terminate();
                    await the_session.close();
                    await client.disconnect();
                    console.log(" Done ");
                    process.exit(0);
                }
            }, timeout);
        });

    }

    console.log(" closing session");
    await the_session.close();
    console.log(" session closed");

    console.log(" Calling disconnect");
    await client.disconnect();

    console.log(chalk.cyan(" disconnected"));

    console.log("success !!   ");
}
treeify.asTree(
    {
        apples: 'gala', //      ├─ apples: gala
        oranges: 'mandarin' //  └─ oranges: mandarin
    },
    true,
    true
);

treeify.asLines(
    {
        apples: 'gala', //                       ├─ apples: gala
        oranges: 'mandarin', //                  ├─ oranges: mandarin
        grapes: {  //                            └─ grapes
            seedless: 'Thompson, Selma Pete', //     ├─ seedless: Thompson, Selma Pete
            seeded: 'concord' //                     └─ seeded: Concord
        }
    },
    true,
    log
);

treeify.asLines(
    {
        apples: 'gala', //      ├─ apples: gala
        oranges: 'mandarin' //  └─ oranges: mandarin
    },
    true,
    false,
    log
);