locka99 / opcua

A client and server implementation of the OPC UA specification written in Rust
Mozilla Public License 2.0
496 stars 131 forks source link

Get children of the parent node for Server side #115

Closed jigar88 closed 3 years ago

jigar88 commented 3 years ago

Is there a way to get the children nodes for the parent node. I am trying to use String node id like "ns=2;s=/Test/Server/Folder". How server can browse this path to get all children of this node?

I know there is a method in python library Node.get_children() , is there something similar available in rust package I can use?

schroeder- commented 3 years ago

You can use find_hierarchical_references

jigar88 commented 3 years ago

I want to process message to structure the opc nodes. for example I am receiving message like

"NJ/Mercer/Pennington/street1/isa"

then I am splitting the string in the vector like

["NJ", "Mercer", "Pennington",  "street1", "isa"]
["NJ", "Mercer", "Pennington",  "street1", "emily"]

Now I am looping through vector elements and want to create nodes as folders for (vector length -1 ) in the structure like

NJ  - folder
  Mercer  - folder
    Pennington  - folder
        Street1 - folder
                      isa - Variable
                      emily -variable

so street folder can have multiple variables based on messages from tcp. so if folders are there then don't recreate but just add variable and if variable also in the folder then update its value.

Also I want to assign variant type for variable based on the variable value, for that I have a function already but not sure how can I set variable type.

I was able to achieve this functionality in python with node class. I tried to use address_space.find_hierarchical_references(&nodeid) from this library but its not finding any nodes , it just returns None. so I can't create a node structure I want. Can you guys direct me ?

Here is the rust code.

static ROOT_NODE_PATH: &'static str = "";

pub fn get_data_type(tag_value: Value) -> Variant {
    match tag_value {
        Value::Null => Variant::Empty,
        Value::Bool(false) => Variant::Boolean,
        Value::Number(Number::from(0)) => Variant::Float,
        Value::String("".parse().unwrap()) => Variant::String,
        _ => VariantTypeId::Empty,
    }
}

pub fn create_nodes_recursively(server: &mut Server, message_chunks: Vec<&str>, tag_value: Value, ns: u16, tag_name: String) {
    let address_space = server.address_space();
    let mut address_space = address_space.write().unwrap();
    let set_variable_data_type = get_data_type(tag_value.clone());
    let mut tag_path = ROOT_NODE_PATH.to_owned();
    let mut root_node_id = NodeId::new(0, 84);
    let mut i = 0;
    while i < message_chunks.len(){
        tag_path += message_chunks[i];
        let make_node_id = NodeId::new(ns, UAString::from(tag_path.clone()));
        if i != message_chunks.len() -1 {
            address_space.add_folder(tag_path.clone(), tag_path.clone(), &root_node_id).unwrap();
        } else {
            address_space.add_variables(
                vec![
                    Variable::new(&make_node_id, &tag_name, &tag_name, &set_variable_data_type)
                ],&(FLODER ID name will be second last element in the message chunks),
            )
        }
        tag_path += "/";
        i += 1
    }
}

Here is the working python code with node class.

def get_tag_data_type(tag_value):
    if type(tag_value) is bool:
        return VariantType.Boolean
    elif type(tag_value) is int:
        return VariantType.Int64
    elif type(tag_value) is float:
        return VariantType.Float
    else:
        return VariantType.String

  def create_nodes_recursively(self, messageChunks, tagValue):
      rootNode = self.root_node
      tagPath = self.root_node_path
      set_data_type = get_tag_data_type(tagValue)
      i = 0
      while i < len(messageChunks):
          try:
              tagPath += messageChunks[i]
              node = self.server.get_node(rootNode)
              if Node(self.server.iserver.isession, tagPath) not in node.get_children():
                  if i != len(messageChunks) - 1:
                      node.add_folder(tagPath, messageChunks[i])
                  else:
                      node.add_variable(tagPath, messageChunks[-1], tagValue, set_data_type)
                      Node(self.server.iserver.isession, self.queueSizePathNodesCreated).set_value(self.index)
                      if self.writeToDynamo:
                          self.db_put.process_dynamodb(tagPath, str(tagValue))
                      self.index += 1
          except uaerrors.BadNodeIdExists:
              pass
          except uaerrors.BadParentNodeIdInvalid:
              pass
          rootNode = tagPath
          tagPath += "/"
          i += 1
locka99 commented 3 years ago

Please also look at this issue - https://github.com/locka99/opcua/issues/105

jigar88 commented 3 years ago

I am trying to create a server so will this issue works for server side ?

On Tue, Jul 6, 2021, 5:43 PM locka99 @.***> wrote:

Please also look at this issue - #105 https://github.com/locka99/opcua/issues/105

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/locka99/opcua/issues/115#issuecomment-875102103, or unsubscribe https://github.com/notifications/unsubscribe-auth/AR7AWCCYYWILWRI5ZULFDG3TWN2I5ANCNFSM47HYU2YQ .

locka99 commented 3 years ago

If it's entirely server side then you can use the find_hierarchical_references() mentioned earlier or copy & adapt that function to follow references that you are interested in.

But you need to know exactly what the parent's node id is otherwise you will not get any children. You can't just stick a path into the node id. The node id is unique to each node in the address space and is whatever you specified when you inserted an object into the address space or what was generated by the API.

If in doubt, look at https://github.com/locka99/opcua/blob/master/samples/simple-server/src/main.rs#L37 where the code creates a folder and is assigned a node id, and then 4 variables with explicit node ids are added inside of it. If I were to call find_hierarchical_references() with the sample_folder_id value in the sample I should expect those variables to be returned as children.

You can also look at the unit test for find_hierarchical_references() at https://github.com/locka99/opcua/blob/c09696db791ef01e449b9f8df4d602ada85c6487/server/src/tests/address_space.rs#L895 where it calls the function with a known node id and the test passes when it finds the expected children.

jigar88 commented 3 years ago

Is there any way to use UAString for parent node id and then use find_hierarchical_references() to get its children?

locka99 commented 3 years ago

I don't know what your string is though. Is it the node id in string form, a browse name or what?

The node id is a structure NodeId which consists of a namespace index and an identifier which is either a string, number, byte string or guid. You can make a NodeId from a string, e.g "ns=2;s=Foo", via its FromStr trait but the string has to be in the canonical representation to parse or it will return Err.

e.g.

if let Ok(node_id) = NodeId::from_str("ns=1;s=Xyz") {
   //..
}

Alternatively if the string holds just the identifier

let node_id = NodeId::from((1, "Xyz"));

A UAString encapsulates a string so you can get the reference to its contents and do the same but you need to know what is in the string to parse it.

I can't help much more than that because I don't know what your string is. But if you created the node in the first place, then you must have the node id that goes with it and can use a map or whatever to cross reference it to the code you're trying to look stuff up with.

jigar88 commented 3 years ago

So I have a vector like

["NJ", "Mercer", "Pennington",  "street1", "isa"]
["NJ", "Mercer", "Pennington",  "street1", "emily"]
["NJ", "Mercer", "Pennington",  "street2", "abby"]

So I am using while loop. So in the above example for the first message NJ which will be folder , Mercer will be folder , Pennington will be folder, street1 will be folder and isa will be variable . isa varable will have string node id as NodeId::from((2, "NJ/Mercer/Pennington/street1/isa"))

NJ ------> NodeId { namespace: 2, identifier: String(UAString { value: Some("NJ") }) }
  Mercer --------> NodeId { namespace: 2, identifier: String(UAString { value: Some("NJ/Mercer") }) }
     Pennington -------> NodeId { namespace: 2, identifier: String(UAString { value: Some("NJ/Mercer/Pennington") }) }
       street1 ----------> NodeId { namespace: 2, identifier: String(UAString { value: Some("NJ/Mercer/Pennington/street1") }) }
         isa ------->  NodeId { namespace: 2, identifier: String(UAString { value: Some("NJ/Mercer/Pennington/street1/isa") }) }

so I am able to create a folder NJ with

address_space.add_folder("NJ", "NJ", &NodeId::objects_folder_id()).unwrap();

but when I tried to use find_hierarchical_references()

parent_node = NodeId { namespace: 2, identifier: String(UAString { value: Some("NJ") }) }
let res = address_space.find_hierarchical_references(parent_node).unwrap();

this results node not exits. Is there a way to access node as string ?

I will have millions of tags in my application so its really not possible to use numeric node id for each folder and variable.

locka99 commented 3 years ago

When you call add_folder() it assigns the node id for you, and returns it in the response. The rationale is most code really doesn't care what node id they're using for a folder and this is a fire and forget function. If you need to specify the node id you can call add_folder_with_id() and supply the node id as a parameter.

These functions are also just wrappers around builders that you could also use, e.g. add_folder_with_id is basically this:

        ObjectBuilder::new(node_id, browse_name, display_name)
            .is_folder()
            .organized_by(parent_node_id.clone())
            .insert(&mut address_space)

From your code above you can set parent_node more easily like so:

parent_node = NodeId::from((2, "NJ"));

The NodeId implements a From<(u16, &'static str)> trait that turns a tuple into a NodeId.

The builders allow you to tailor make any node you like in the address space. The add_folder_with_id() uses a builder to make an object node which is a folder, make it organized by a parent and insert it into the address space.

I am going to close this issue because it is not so much a bug as it is data management / structure in the code you are using to build your address space - assign unique identifiable NodeIds to your nodes and have some easy way to look them up. Personally I think I would just maintain my own lookups for this stuff rather than navigate the address space to find things I inserted in the first place.

I am going to open a related issue that it might be useful eventually which is to make TranslateBrowsePathsToNodeIds functionality in the view service available as a helper translate_browse_paths_to_node_ids() but I'm not advocating you use it here.

locka99 commented 3 years ago

The new issue is #123 for a helper function for relative path requests

jigar88 commented 3 years ago

I am able to achieve what I wanted, it turned out to be very simple fix. Thanks for all your help. :-)