mikakaraila / node-red-contrib-opcua

A Node-RED node to communicate OPC UA. Uses node-opcua library.
Other
211 stars 196 forks source link

Acknowledge Alarm error BadNodeIdUnknown #338

Closed boualid closed 3 years ago

boualid commented 3 years ago

hello,

Currently i am developing OPCUA client using Node Red. I am able to read the event/Alarm from OPC UA server. now i want to acknowledge the same , so i had tried create a data flow, with OPCUA IIoT nodes , that allow to call acknowledgement method for opcua. Here it is:

image

the content of the node function: Remark: i took the value of eventID from UA Expert

image

the problem is when i execute the flow i get the below error message:

image

it seems that the problem is related to Event ID but i didn't know how convert it to ByteString. could you please help me

thanks

mikakaraila commented 3 years ago

I have coded this with Typescript. Have to take a look how to do same as flow.

mikakaraila commented 3 years ago

Just mention that OPCUA IIoT nodes are from another package.

boualid commented 3 years ago

thank you for the prompt response, could you please send me the path in witch you share the code. thank you

mikakaraila commented 3 years ago

I looked my code and it is totally different. I suppose I have to add it into the client node as own operation.

mikakaraila commented 3 years ago

I need to test it a bit, but just few lines.

boualid commented 3 years ago

could you please share your test. that'll help me to understand the code behaviour. thank you

mikakaraila commented 3 years ago

In my code eventId seems to be problem:

    async function acknowledge_input(msg) {
      console.log(chalk.cyan("A&C MSG: ") + stringify(msg.payload));

      // ConditionId == Alarm object nodeId like Prosys ns=6;s=MyLevel.Alarm eventId is ByteString that identifies event instance
      // Event payload object
      // {"EventId":"0x00000000000000f100000000000000ee","SourceName":"MyLevel","Time":"2021-09-01T18:43:44.820Z","ReceiveTime":"2021-09-01T18:43:44.820Z","Message":"Level exceeded","Severity":500,"Retain":true,"EnabledState":"Enabled","Quality":{"value":0},"StatusText":"Good (0x00000)","AckedState":"Unacknowledged","ActiveState":"Inactive","HighLimit":70,"LowLimit":30,"HighHighLimit":90,"LowLowLimit":10}
      verbose_log("Acknowledge: " + msg.topic + " conditionId: " + msg.nodeId + " eventId: " + msg.eventId + " comment: " + msg.comment);
      try {
        const status = await node.session.acknowledgeCondition(coerceNodeId(msg.nodeId), opcua.coerceByteString(msg.eventId), opcua.coerceLocalizedText(msg.comment));
        if (status !== opcua.StatusCodes.Good) {
          node_error(node.name + " error at acknowledge, status: " + status.toString());
          set_node_errorstatus_to("error", status.toString());  
        }
      }
      catch(err) {
        node_error(node.name + " error at acknowledge: " + stringify(err));
        set_node_errorstatus_to("error", err);
      }
    }

image

mikakaraila commented 3 years ago

I asked this from @erossignon and he will check this. EventId and nodeId are correct but Prosys UA Simulation Server has branch value null and inside node-opcua this is causing BadEventIdUnknown error code. Let´s wait...

boualid commented 3 years ago

ok, thanks a lot Mika

mikakaraila commented 3 years ago

Now it works: image

mikakaraila commented 3 years ago

I will publish this one as starting point. I will add alarm objects to server. image

Function node contains now needed parameters: image

mikakaraila commented 3 years ago

Available v0.2.237

boualid commented 3 years ago

hello mika,

First of all, i want to thank you for your collaboration. I tried several times to generate an acknowledgement to an alarm, by flowing your example, but unfortunately i did not success. In fact each time i activate the flow, Node-RED stop to run.

Please find, on the below screen shot, the details of what i did: 1- the tree structure of the alarms image 2- the parameters of the node function: image

i think the problem is at the value assigned to the parameters. could you help me. thank you.

mikakaraila commented 3 years ago

Can you check actual ns=1;s=Green.Alarm1.Alm04.EventId value? There seems to be [20] before actual 0x ByteString value. Is it just your tool that shows it.

mikakaraila commented 3 years ago

This is Prosys UA Simulation Server address space, note that MyLevel.Alarm object must contain: 1) Acknowledge method and 2) EventId

image

boualid commented 3 years ago

hello,

thank you

boualid commented 3 years ago

hello, please find the content of the message error when i run the flow

image

mikakaraila commented 3 years ago

Sorry msg.topic must contain nodeId, see code below:

image

mikakaraila commented 3 years ago

Don' t mix msg.topic and msg.conditionId to different alarm object / eventId.

You can run "node-red -v FLOW.json" then verbose is on and parameter will become to console.

mikakaraila commented 3 years ago

Please check nodeId for both objects with UaExpert.

boualid commented 3 years ago

event i wrote the same code, like you mentioned, in the node function, but i always got eventId=null. please take a look on the below error message. function_2 function

should i add msg.eventId in my funtion or is there something missing from me?

thank you

mikakaraila commented 3 years ago

Typo or wrong nodeId, I asked you to check actual nodeId with UaExpert. UaExpert shows in hiearchy browseName, not actual nodeId. Look panel on right:

image

I expect you are missing part of the NodeId, perhaps it is: msg.topic = "ns=1;s=Green.Alarm1.Alm04"; msg.conditionId = "ns=1;s=Green.Alarm1.Alm04.EventId";

image

boualid commented 3 years ago

i am sorry, i misunderstand. i have a question: in the case of the value of NodeId = ns=1;i=1001, should i assign msg topic and conditionId like below:

thank you for your patience and collaboration

mikakaraila commented 3 years ago

Yes if they are Alm03 + EventId for it. Or Alm04 + EventId for it.

Did it work?

mikakaraila commented 3 years ago

You can acknowledge only active alarm

mikakaraila commented 3 years ago

Alarm have states and also it can be non acknowledgeable alarm

boualid commented 3 years ago

Yes, i acknowledge only the active alarm witch has AckedState === false. the problem is that I can not understand why the "acknowledgeCondition" function, in client_tools.ts file, returns errors although I am sure that the values that I pass in parameter are correct (conditionId and EventId). if you don't mind, i send you my OPCUA Server in private in order make test in your side.

thank you

mikakaraila commented 3 years ago

Ok, no problem. I had long weekend.

boualid commented 3 years ago

hello, please find the entire code of my opcua server

var opcua = require("node-opcua");

var server = new opcua.OPCUAServer({
    port: 4334, // the port of the listening socket of the server
    resourcePath: "UA/MyLittleServer", // this path will be added to the endpoint resource name
    buildInfo: {
        productName: "MySampleServer1",
        buildNumber: "7658",
        buildDate: new Date(2021, 8, 28)
    }
});

server.initialize(post_initialize);

function post_initialize() {
    console.log("initialized");
    construct_my_address_space(server, function () {
        server.start(function () {
            console.log("Server is now listening ... ( press CTRL+C to stop)");
            console.log("port ", server.endpoints[0].port);
            var endpointUrl = server.endpoints[0].endpointDescriptions()[0].endpointUrl;
            console.log(" the primary server endpoint url is ", endpointUrl);
        });
    });

}

function construct_my_address_space(server, callback) {

    var addressSpace = server.engine.addressSpace;

    // declare a new object
    var device = addressSpace.addObject({
        organizedBy: addressSpace.rootFolder.objects,
        browseName: "MyDevice"
    });

    // add a variable named MyVariable1 to the newly created folder "MyDevice"
    var variable1 = 1;

    // emulate variable1 changing every 500 ms
    setInterval(function () { variable1 += 1; }, 500);

    addressSpace.addVariable({
        componentOf: device,
        nodeId: "ns=1;s=variable_1",
        browseName: "MyVariable1",
        dataType: "Double",
        value: {
            get: function () {
                return new opcua.Variant({ dataType: opcua.DataType.Double, value: variable1 });
            }
        }
    });
    addressSpace.installAlarmsAndConditionsService();
    var green = addressSpace.addObject({
        browseName: "Green",
        eventNotifier: 0x1,
        notifierOf: addressSpace.rootFolder.objects.server,
        organizedBy:addressSpace.rootFolder.objects.server
    });
        source = addressSpace.addObject({
            browseName: "Alarm1",
            componentOf: green,
            eventSourceOf: green
    });

setTimeout(alarm, 5000, "Alm01", addressSpace, "Alm01", "MyFirstAlarm", 800, "Alm01, created by oualid");
setTimeout(alarm, 10000, "Alm02", addressSpace, "Alm02", "MyFirstAlarm", 200, "Alm02, created by oualid");

callback();

}
function alarm(condition, addressSpace, nameOfBrowse1, nameOfBrowse2, severity , msg){
    var alarmSourceTime = new Date(); // From external source
    const NodeId = require("node-opcua-nodeid").NodeId;
        const alarmConditionType = addressSpace.findEventType("AlarmConditionType");
        const alarm = alarmConditionType.instantiate({
            componentOf: source,
            conditionSource: source,
            browseName: nameOfBrowse1
        });

    var alarmNode = addressSpace.instantiateAlarmCondition("AlarmConditionType", {
        conditionName: "Error",
        conditionClass: "ProcessConditionClassType",
        conditionSource: source,
        inputNode: NodeId.NullNodeId,
        //componentOf: NodeId.NullNodeId,
        browseName: nameOfBrowse2,
    }, []);
    var branch = alarmNode.currentBranch();
    alarmNode.setSourceName(condition);
    branch._set_var("sourceName", "String", condition);
    alarmNode.activateAlarm();
    branch.setMessage(msg);
    branch.setSeverity(severity);
    branch.setTime(alarmSourceTime);
    branch.setReceiveTime(new Date());
    branch.setAckedState(false);
    branch.setActiveState(false);    
    branch.renewEventId();
    alarmNode.raiseConditionEvent(branch, true);
}

thank you

mikakaraila commented 3 years ago

I have to fix first your code, now it works, but I added/fixed instantiation. I expect it was creating new nodeId always. It made is impossible to acknowledge alarm as new object was created. image

mikakaraila commented 3 years ago

Works, now. I made changes to your server.js. Just quick ones, you need to modify it more perhaps... image

mikakaraila commented 3 years ago

Here:

var opcua = require("node-opcua");

async function main() {
    var server = new opcua.OPCUAServer({
        port: 4334, // the port of the listening socket of the server
        resourcePath: "/UA/MyLittleServer/", // this path will be added to the endpoint resource name
        buildInfo: {
            productName: "MySampleServer1",
            buildNumber: "7658",
            buildDate: new Date(2021, 8, 28)
        }
    });

    await server.start();

    console.log("Server is now listening ... ( press CTRL+C to stop)");
    console.log("port ", server.endpoints[0].port);
    var endpointUrl = server.endpoints[0].endpointDescriptions()[0].endpointUrl;
    console.log(" the primary server endpoint url is ", endpointUrl);

    post_initialize(server);

}

function post_initialize(server) {
    console.log("initialized");
    construct_my_address_space(server, function () {
        console.log("Address space created!")        
    });

}

function construct_my_address_space(server, callback) {

    var addressSpace = server.engine.addressSpace;
    var namespace = addressSpace.getOwnNamespace();
    // declare a new object
    var device = namespace.addObject({
        organizedBy: addressSpace.rootFolder.objects,
        browseName: "MyDevice"
    });

    // add a variable named MyVariable1 to the newly created folder "MyDevice"
    var variable1 = 1;

    // emulate variable1 changing every 500 ms
    setInterval(function () { variable1 += 1; }, 500);

    var variable = namespace.addVariable({
        componentOf: device,
        nodeId: "ns=1;s=variable_1",
        browseName: "MyVariable1",
        dataType: "Double",
        value: {
            get: function () {
                return new opcua.Variant({ dataType: opcua.DataType.Double, value: variable1 });
            }
        }
    });
    server.engine.addressSpace.installAlarmsAndConditionsService();

    var green = namespace.addObject({
        // parent: device,
        organizedBy: device,
        // propertyOf: device,
        browseName: "Green",
        eventNotifier: 0x1,
        notifierOf: addressSpace.rootFolder.objects.server,
        organizedBy:addressSpace.rootFolder.objects.server
    });
        source = namespace.addObject({
            browseName: "Alarm1",
            nodeId: "ns=1;s=Alarm1",
            componentOf: green,
            eventSourceOf: green
    });

setTimeout(alarm, 5000, "Alarm1-Alm01", addressSpace, "Alarm1-Alm01", "MyFirstAlarm", 800, "Alarm1-Alm01, created by oualid", source);
setTimeout(alarm, 10000, "Alarm1-Alm02", addressSpace, "Alarm1-Alm02", "MyFirstAlarm", 200, "Alarm1-Alm02, created by oualid", source);

callback();

}

function alarm(condition, addressSpace, nameOfBrowse1, nameOfBrowse2, severity , msg, source){
    var alarmSourceTime = new Date(); // From external source
    var NodeId = require("node-opcua-nodeid").NodeId;
    const alarmConditionType = addressSpace.findEventType("AlarmConditionType");
    const alarm = alarmConditionType.instantiate({
        componentOf: source,
        conditionSource: source,
        browseName: nameOfBrowse1  
    });
    var namespace = addressSpace.getOwnNamespace();
    var alarmNode = addressSpace.findNode("ns=1;s=" + nameOfBrowse2);
    if (!alarmNode) {
        alarmNode = namespace.instantiateAlarmCondition("AlarmConditionType", {
            conditionName: "Error",
            conditionClass: "ProcessConditionClassType",
            conditionSource: source,
            inputNode: source, // NodeId.NullNodeId,
            //componentOf: NodeId.NullNodeId,
            browseName: nameOfBrowse1,
            nodeId: "ns=1;s=" + nameOfBrowse1
        }, []);
    }
    var branch = alarmNode.currentBranch();
    alarmNode.setSourceName(condition);
    // branch._set_var("sourceName", "String", condition);
    alarmNode.activateAlarm();
    branch.setMessage(msg);
    branch.setSeverity(severity);
    branch.setTime(alarmSourceTime);
    branch.setReceiveTime(new Date());
    branch.setAckedState(false);
    branch.setActiveState(true); // Must be Active
    branch.renewEventId();
    const eventId = addressSpace.generateEventId();
    // console.log("EventId node:" + alarmNode.eventId.toString() + " value: " + eventId.toString());
    const ret = alarmNode.eventId.setValueFromSource({dataType: opcua.DataType.ByteString, value: eventId.value});
    alarmNode.raiseConditionEvent(branch, true);
}

main()
boualid commented 3 years ago

hello Mika , first of all , i want to thank you very much for your help. Unlucky, Even i copied your code, i still have a problem and i am so sorry for that.

  1. i don't know why in the field DataAccessView i get an error message and i don't get the event id. screen1
  2. i want to validate with you if you have wrote the right parameter in Function node. image

thanks a lot for your help

boualid commented 3 years ago

it's possible please provide me your flow that you used to test the acknowledge function. thank you

mikakaraila commented 3 years ago

Here, just keep in mind that you don´t create new nodes, it´s enough to raise new condition. MYLITTLESERVER.txt

mikakaraila commented 3 years ago

Can be that I modified code and didn´t test it again. Sorry, but I expect you can learn more by studying it as you fix it.

This code snippet you should look:

    const eventId = addressSpace.generateEventId();
    console.log("EventId node:" + alarmNode.eventId.nodeId.toString() + " value: " + eventId.toString("hex")); // For node-red
    const ret = alarmNode.eventId.setValueFromSource({dataType: opcua.DataType.ByteString, value: eventId.value});

image

boualid commented 3 years ago

thank you very much