opcua-asyncio icon indicating copy to clipboard operation
opcua-asyncio copied to clipboard

Unable to get_child() from UaModeller xml nodeset

Open om327 opened this issue 2 years ago • 8 comments

Hello,

Im trying to import a simple XML nodeset from UaModeller but I keep getting the following error:

Traceback (most recent call last):
  File "c:\Users\SVL\OneDrive - University of Cambridge\unified-architecture\opcua-server\xml-server.py", line 25, in <module>
    asyncio.run(main())
  File "C:\Users\SVL\AppData\Local\Programs\Python\Python39-32\lib\asyncio\runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "C:\Users\SVL\AppData\Local\Programs\Python\Python39-32\lib\asyncio\base_events.py", line 647, in run_until_complete
    return future.result()
  File "c:\Users\SVL\OneDrive - University of Cambridge\unified-architecture\opcua-server\xml-server.py", line 15, in main
    knauer = await objects.get_child("1:Kanuer")
  File "C:\Users\SVL\AppData\Local\Programs\Python\Python39-32\lib\site-packages\asyncua\common\node.py", line 503, in get_child
    result.StatusCode.check()
  File "C:\Users\SVL\AppData\Local\Programs\Python\Python39-32\lib\site-packages\asyncua\ua\uatypes.py", line 328, in check
    raise UaStatusCodeError(self.value)
asyncua.ua.uaerrors._auto.BadNoMatch: "The requested operation has no match to return."(BadNoMatch)

My code is based off the example xml-server and comprises of a single object "knauer" and a variable "Pressure" which is being populated with a random number ever 100ms. Simply importing the XML without trying to link to the nodes works with the nodes appearing in UaExpert. However, when I try to link to the nodes using get_child() I get an error?

import asyncio
from asyncua import ua, Server
from asyncua.common.methods import uamethod
import random

async def main():

    server = Server()
    server.set_endpoint('opc.tcp://0.0.0.0:4840')

    await server.init()
    await server.import_xml(r'opcua-server\p4_1s.xml')

    objects = server.nodes.objects
    knauer = await objects.get_child("1:Kanuer")

    pressure = await knauer.get_child("1:Pressure")

    async with server:
        while True:
            await pressure.write_value(random.random())
            await asyncio.sleep(0.1)
            
if __name__ == '__main__':
    asyncio.run(main())

My model was generated using UaModeller.

<?xml version="1.0" encoding="utf-8"?>
<UANodeSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:uax="http://opcfoundation.org/UA/2008/02/Types.xsd" xmlns="http://opcfoundation.org/UA/2011/03/UANodeSet.xsd" xmlns:s1="http://github.com/om327/unified-automation/Types.xsd" xmlns:ua="http://unifiedautomation.com/Configuration/NodeSet.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <NamespaceUris>
        <Uri>http://github.com/om327/unified-automation</Uri>
    </NamespaceUris>
    <Aliases>
        <Alias Alias="Boolean">i=1</Alias>
        <Alias Alias="Double">i=11</Alias>
        <Alias Alias="String">i=12</Alias>
        <Alias Alias="DateTime">i=13</Alias>
        <Alias Alias="Organizes">i=35</Alias>
        <Alias Alias="HasTypeDefinition">i=40</Alias>
        <Alias Alias="HasProperty">i=46</Alias>
        <Alias Alias="HasComponent">i=47</Alias>
        <Alias Alias="IdType">i=256</Alias>
        <Alias Alias="NumericRange">i=291</Alias>
    </Aliases>
    <Extensions>
        <Extension>
            <ua:ModelInfo Tool="UaModeler" Hash="oDfr+Nds+p0BjkomhYVqGA==" Version="1.6.6"/>
        </Extension>
    </Extensions>
    <UAObject NodeId="ns=1;i=5001" BrowseName="1:Knauer">
        <DisplayName>Knauer</DisplayName>
        <References>
            <Reference ReferenceType="HasTypeDefinition">i=58</Reference>
            <Reference ReferenceType="HasComponent">ns=1;i=6008</Reference>
            <Reference ReferenceType="Organizes" IsForward="false">i=85</Reference>
        </References>
    </UAObject>
    <UAVariable DataType="Double" NodeId="ns=1;i=6008" BrowseName="1:Pressure" AccessLevel="3">
        <DisplayName>Pressure</DisplayName>
        <References>
            <Reference ReferenceType="HasTypeDefinition">i=63</Reference>
            <Reference ReferenceType="HasComponent" IsForward="false">ns=1;i=5001</Reference>
        </References>
        <Value>
            <uax:Double>0</uax:Double>
        </Value>
    </UAVariable>
    <UAObject SymbolicName="http___github_com_om327_unified_automation" NodeId="ns=1;i=5002" BrowseName="1:http://github.com/om327/unified-automation">
        <DisplayName>http://github.com/om327/unified-automation</DisplayName>
        <References>
            <Reference ReferenceType="HasTypeDefinition">i=11616</Reference>
            <Reference ReferenceType="HasComponent" IsForward="false">i=11715</Reference>
            <Reference ReferenceType="HasProperty">ns=1;i=6001</Reference>
            <Reference ReferenceType="HasProperty">ns=1;i=6002</Reference>
            <Reference ReferenceType="HasProperty">ns=1;i=6003</Reference>
            <Reference ReferenceType="HasProperty">ns=1;i=6004</Reference>
            <Reference ReferenceType="HasProperty">ns=1;i=6005</Reference>
            <Reference ReferenceType="HasProperty">ns=1;i=6006</Reference>
            <Reference ReferenceType="HasProperty">ns=1;i=6007</Reference>
        </References>
    </UAObject>
    <UAVariable DataType="Boolean" ParentNodeId="ns=1;i=5002" NodeId="ns=1;i=6001" BrowseName="IsNamespaceSubset">
        <DisplayName>IsNamespaceSubset</DisplayName>
        <References>
            <Reference ReferenceType="HasTypeDefinition">i=68</Reference>
            <Reference ReferenceType="HasProperty" IsForward="false">ns=1;i=5002</Reference>
        </References>
        <Value>
            <uax:Boolean>false</uax:Boolean>
        </Value>
    </UAVariable>
    <UAVariable DataType="DateTime" ParentNodeId="ns=1;i=5002" NodeId="ns=1;i=6002" BrowseName="NamespacePublicationDate">
        <DisplayName>NamespacePublicationDate</DisplayName>
        <References>
            <Reference ReferenceType="HasTypeDefinition">i=68</Reference>
            <Reference ReferenceType="HasProperty" IsForward="false">ns=1;i=5002</Reference>
        </References>
        <Value>
            <uax:DateTime>2022-07-22T00:15:42Z</uax:DateTime>
        </Value>
    </UAVariable>
    <UAVariable DataType="String" ParentNodeId="ns=1;i=5002" NodeId="ns=1;i=6003" BrowseName="NamespaceUri">
        <DisplayName>NamespaceUri</DisplayName>
        <References>
            <Reference ReferenceType="HasTypeDefinition">i=68</Reference>
            <Reference ReferenceType="HasProperty" IsForward="false">ns=1;i=5002</Reference>
        </References>
        <Value>
            <uax:String>http://github.com/om327/unified-automation</uax:String>
        </Value>
    </UAVariable>
    <UAVariable DataType="String" ParentNodeId="ns=1;i=5002" NodeId="ns=1;i=6004" BrowseName="NamespaceVersion">
        <DisplayName>NamespaceVersion</DisplayName>
        <References>
            <Reference ReferenceType="HasTypeDefinition">i=68</Reference>
            <Reference ReferenceType="HasProperty" IsForward="false">ns=1;i=5002</Reference>
        </References>
        <Value>
            <uax:String>1.0.0</uax:String>
        </Value>
    </UAVariable>
    <UAVariable DataType="IdType" ParentNodeId="ns=1;i=5002" ValueRank="1" NodeId="ns=1;i=6005" ArrayDimensions="0" BrowseName="StaticNodeIdTypes">
        <DisplayName>StaticNodeIdTypes</DisplayName>
        <References>
            <Reference ReferenceType="HasTypeDefinition">i=68</Reference>
            <Reference ReferenceType="HasProperty" IsForward="false">ns=1;i=5002</Reference>
        </References>
    </UAVariable>
    <UAVariable DataType="NumericRange" ParentNodeId="ns=1;i=5002" ValueRank="1" NodeId="ns=1;i=6006" ArrayDimensions="0" BrowseName="StaticNumericNodeIdRange">
        <DisplayName>StaticNumericNodeIdRange</DisplayName>
        <References>
            <Reference ReferenceType="HasTypeDefinition">i=68</Reference>
            <Reference ReferenceType="HasProperty" IsForward="false">ns=1;i=5002</Reference>
        </References>
    </UAVariable>
    <UAVariable DataType="String" ParentNodeId="ns=1;i=5002" NodeId="ns=1;i=6007" BrowseName="StaticStringNodeIdPattern">
        <DisplayName>StaticStringNodeIdPattern</DisplayName>
        <References>
            <Reference ReferenceType="HasTypeDefinition">i=68</Reference>
            <Reference ReferenceType="HasProperty" IsForward="false">ns=1;i=5002</Reference>
        </References>
    </UAVariable>
</UANodeSet>

interestingly there was another error when importing if the following lines were present in the XML:

    <Models>
        <Model ModelUri="http://github.com/om327/unified-automation" PublicationDate="2022-07-22T00:15:42Z" Version="1.0.0">
            <RequiredModel ModelUri="http://opcfoundation.org/UA/" PublicationDate="2022-01-24T00:00:00Z" Version="1.05.01"/>
        </Model>
    </Models>

Any help would be fantastic! Thanks!

om327 avatar Jul 22 '22 01:07 om327

First of for such kind of problems it is always good to check your addresspace via a testclient such as UAExpert.

My therory is that your namespace index is not 1 so maybe try this:

import asyncio
from asyncua import ua, Server
from asyncua.common.methods import uamethod
import random

async def main():

    server = Server()
    server.set_endpoint('opc.tcp://0.0.0.0:4840')

    await server.init()
    await server.import_xml(r'opcua-server\p4_1s.xml')
    ns = await server.get_namespace_index("http://github.com/om327/unified-automation")
    objects = server.nodes.objects
    knauer = await objects.get_child(f"{ns}:Kanuer")

    pressure = await knauer.get_child(f"{ns}:Pressure")

    async with server:
        while True:
            await pressure.write_value(random.random())
            await asyncio.sleep(0.1)
            
if __name__ == '__main__':
    asyncio.run(main())

We currently ship 1.04.XX nodesets in the future we will update to 1.05.XX. Maybe you can change the required nodeset some where in UAModller.

schroeder- avatar Jul 22 '22 08:07 schroeder-

Thanks @schroeder the get_namespace() function returns a value of 2, whereas in the XML this is defined as 1:

 <UAObject NodeId="ns=1;i=5001" BrowseName="1:Knauer">

Unfortunately, substituting the namespace value using your code still returns the same error?

Intrestingly in UaModeller the kanuer objectType has a namespace of 1.

image

Importing and running the XML without trying to bind will give me the correct server in UaExpert:

import asyncio
from asyncua import ua, Server
from asyncua.common.methods import uamethod
import random

async def main():

    server = Server()
    server.set_endpoint('opc.tcp://0.0.0.0:4840')

    await server.init()
    await server.import_xml(r'opcua-server\p4_1s.xml')

    async with server:
        while True:
            await asyncio.sleep(0.1)

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

image

Is it something to do with the the XML parser or has UaModeller changed the way it outputs XML schema?

Thanks again!

om327 avatar Jul 22 '22 10:07 om327

First of the namespace ids in nodeset are relative. So 1 just means its using the first namespace in the nodeset xml. Othwise it would be impossible to load multiple nodesets. The namespaces idx depends on insertation order of new namespaces. Every new namespace gets the a new idx. 0 -> opc ua namespace 1 -> toolkit namespace 2 -> first registered namespace 3 -> second registerd namespace ...

Most opc ua librarys reserve namespace 1 for internal use.

schroeder- avatar Jul 22 '22 11:07 schroeder-

Hi @schroeder- daft question, when using a tool like UaModeller how do we import multiple XML schema?

Do these need to be imported as separate XML files with different URLs or one XML file with the different URLs in the schema?


    await server.init()

    await server.import_xml(r'r-series.xml')
    ns = await server.get_namespace_index("http://github.com/om327/unified-architecture/r-series")

    await server.import_xml(r'gx271.xml')
    ns2 = await server.get_namespace_index("http://github.com/om327/unified-architecture/gx271")

This doesn't seem to work which makes me think I should import just one XML?

om327 avatar Aug 09 '22 11:08 om327

Importing multiple xml is supported, maybe there is something else wrong. It would be easier if you could post a sample xml file so I can check what is going wrong.

schroeder- avatar Aug 15 '22 14:08 schroeder-

Hi @schroeder- ,

Thanks for getting back to me - I have some code for some lab equipment I can send you. It was created using UaModeller - I must admit I'm a bit new to OPCUA and using XML models.

The grand plan would be to dynamically import XML models for equipment that is detected on a network - is this possible with python. I've been playing with python dictionaries for looping through the XML nodes address. This is so that for instances of there being two pieces of the same equipment they can both be loaded into the server as two separate objects but using the same XML - is this the best wat to do this, are there any examples of this type of use case?

Really appreciate the help!

om327 avatar Aug 15 '22 19:08 om327

It is possible to import multiple XML models. But they either should have their own namespace or at least no colliding Nodeids, which can be tricky with UaModeller. Also another approach would be to delete unused nodes. Also you could just create ObjectTypes/VariableTypes in your XML and instantiate the Objects and Variables as needed, to match the equipment.

schroeder- avatar Aug 17 '22 13:08 schroeder-

Trivial thing, and I assume this is just a typo when giving the information: Th call stack says "Kanuer", the XML says "Knauer". You've ruled out that error source, right? (Because that kind of typo would give that exact error)

starturtle avatar Aug 17 '22 14:08 starturtle