Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

get_path created BrowsePath finds not match via tranlate_browsepaths #1706

Open
Fliens opened this issue Sep 5, 2024 · 12 comments
Open

get_path created BrowsePath finds not match via tranlate_browsepaths #1706

Fliens opened this issue Sep 5, 2024 · 12 comments

Comments

@Fliens
Copy link

Fliens commented Sep 5, 2024

This is a BUG REPORT for issues in the existing code.

Describe the bug

I'm using the get_path(as_string=True) function from the node class.
The function chooses a 'wrong' path so that the translate_browsepaths() function generates the status code "BadNoMatch"

The Prosys Browser can also export a browse path (right click on node) which does find a match via the translate_browsepaths() function.

The node 'exists' in both paths but only the path from prosys works.

To Reproduce

Steps to reproduce the behavior incl code:

import asyncio

from asyncua import Client, ua
from asyncua.ua.status_codes import get_name_and_doc


async def main():
    url = "opc.tcp://uademo.prosysopc.com:53530/OPCUA/SimulationServer/"

    async with Client(url=url) as client:
        # The node these paths should lead to:
        # ns=4;s=1001/0:Direction
        # Working path: /Objects/3:Simulation/3:Counter/2:Signal/4:Direction

        results: ua.BrowsePathResult = await client.translate_browsepaths(
            starting_node=client.nodes.root.nodeid,
            relative_paths=[
                "/Objects/3:Simulation/3:Counter/2:Signal/4:Direction",
                "/0:Objects/0:Server/2:ValueSimulations/2:Signal/4:Direction",
            ],
        )

        for x in results:
            print(get_name_and_doc(x.StatusCode.value), x)

        # Output
        # ('Good', 'The operation succeeded.') BrowsePathResult(StatusCode_=StatusCode(value=0), Targets=                [BrowsePathTarget(TargetId=ExpandedNodeId(Identifier='1001/0:Direction', NamespaceIndex=4, NodeIdType=        <NodeIdType.String: 3>, NamespaceUri=None, ServerIndex=0), RemainingPathIndex=4294967295)])
        # ('BadNoMatch', 'The requested operation has no match to return.')         BrowsePathResult(StatusCode_=StatusCode(value=2154758144), Targets=[])



if __name__ == "__main__":
    asyncio.run(main())

The browse path that was exported from Prosys works: /Objects/3:Simulation/3:Counter/2:Signal/4:Direction

Expected behavior

The function get_path(as_string=True) should generate a path that works with the translate_browsepaths() function.

Screenshots

image

Version

Python-Version:3.11.5

opcua-asyncio Version (e.g. master branch, 0.9):

@Fliens Fliens changed the title Missing node in browsepath get_path created BrowsePath finds not match via tranlate_browsepaths Sep 5, 2024
@Fliens
Copy link
Author

Fliens commented Sep 5, 2024

A way to get all paths of a node would be nice too
This way you could export the paths and later on reconstruct the address space 1:1

@AndreasHeine
Copy link
Member

A way to get all paths of a node would be nice too This way you could export the paths and later on reconstruct the address space 1:1

as far as i know not part of OPC UA Spec.! For TranslateBrowsePaths the client needs to know the path to the node, from which the client wants the nodeid from!

@Fliens
Copy link
Author

Fliens commented Sep 5, 2024

A way to get all paths of a node would be nice too This way you could export the paths and later on reconstruct the address space 1:1

as far as i know not part of OPC UA Spec.! For TranslateBrowsePaths the client needs to know the path to the node, from which the client wants the nodeid from!

Yeah that's what I want to do but the get_path function returns a path in the case above that can't be translated to the actual node.

From my understanding the get_path function should always return a valid path that can be translated but that does not seem to be the case.

@AndreasHeine
Copy link
Member

the only difference is:
"/Objects"
"/0:Objects"
right?

@Fliens
Copy link
Author

Fliens commented Sep 5, 2024

the only difference is: "/Objects" "/0:Objects" right?

No this does not affect the translate_browsepaths() function

The node in question is referenced twice (as shown in the screenshot on the right side).
Both paths are valid by eye (when following the nodes in the browser gui) but only the first path (exported from the prosys gui) works with the translate_browsepaths() function.

@AndreasHeine
Copy link
Member

get_path just uses the first reference see:

refs = await node.get_references(refs=ua.ObjectIds.HierarchicalReferences, direction=ua.BrowseDirection.Inverse)

@AndreasHeine
Copy link
Member

browsename is correctly formatted:

class QualifiedName:

and the relative path implementation looks right on the first look
https://github.com/FreeOpcUa/opcua-asyncio/blob/master/asyncua/ua/relative_path.py
https://reference.opcfoundation.org/Core/Part4/v105/docs/A.2#TableA.2

@Fliens
Copy link
Author

Fliens commented Sep 5, 2024

Yeah I guess so but this seems counterintuitive since I would assume that I could directly put the path from the get_path function (transformed into a valid string) into the translate_path function and would get the same node back

@AndreasHeine
Copy link
Member

but both paths are valid!

@Fliens
Copy link
Author

Fliens commented Sep 5, 2024

but both paths are valid!

But the second one does not work.
('BadNoMatch', 'The requested operation has no match to return.')

You can run the code yourself and connect to the server yourself (via gui), the opcua server in the example is public.

I've extended the example code to include the just read path via get_path()

import asyncio

from asyncua import Client, Node, ua
from asyncua.ua.status_codes import get_name_and_doc


async def main():
    url = "opc.tcp://uademo.prosysopc.com:53530/OPCUA/SimulationServer/"

    async with Client(url=url) as client:
        # The node these paths should lead to:
        # ns=4;s=1001/0:Direction
        # Working path: /Objects/3:Simulation/3:Counter/2:Signal/4:Direction

        test_node: Node = client.get_node("ns=4;s=1001/0:Direction")
        test_path = "/".join(
            (await test_node.get_path(as_string=True))[1:]
        )  # 0:Objects/0:Server/2:ValueSimulations/2:Signal/4:Direction

        results: ua.BrowsePathResult = await client.translate_browsepaths(
            starting_node=client.nodes.root.nodeid,
            relative_paths=[
                "0:Objects/3:Simulation/3:Counter/2:Signal/4:Direction",
                "0:Objects/0:Server/2:ValueSimulations/2:Signal/4:Direction",
                test_path,  # same as the one above
            ],
        )

        for x in results:
            print(get_name_and_doc(x.StatusCode.value), x)

        # Result:
        # ('Good', 'The operation succeeded.') BrowsePathResult(StatusCode_=StatusCode(value=0), Targets=[BrowsePathTarget(TargetId=ExpandedNodeId(Identifier='1001/0:Direction', NamespaceIndex=4, NodeIdType=<NodeIdType.String: 3>, NamespaceUri=None, ServerIndex=0), RemainingPathIndex=4294967295)])
        # ('BadNoMatch', 'The requested operation has no match to return.') BrowsePathResult(StatusCode_=StatusCode(value=2154758144), Targets=[])
        # ('BadNoMatch', 'The requested operation has no match to return.') BrowsePathResult(StatusCode_=StatusCode(value=2154758144), Targets=[])


if __name__ == "__main__":
    asyncio.run(main())

@AndreasHeine
Copy link
Member

"BadNoMatch" is a response from Server!
The Paths are correct and valid according to the OPC UA Spec.!

We just use the first hierarchical Reference and follow them up ("inverse" direction)... it does not matter which you chose because they point to the same NodeId/Node...

@Fliens
Copy link
Author

Fliens commented Sep 6, 2024

Yes I understand that the error is a response from the server.
And yeah I checked with the spec, the paths are valid.

I would prefer that the get_path would return all paths but that is my issue since that functionality is not a part of the spec.

But I still don't understand why the translate_browsepaths() function does not find a match.

The code in this comment creates a valid path that the translate service on the server somehow can't resolve.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants