FreeOpcUa / opcua-asyncio

OPC UA library for python >= 3.7
GNU Lesser General Public License v3.0
1.12k stars 361 forks source link

DataType syntax #926

Open om327 opened 2 years ago

om327 commented 2 years ago

Hello,

I've been working on integrating some asyncio servers with LabVIEW's SystemLink OPC UA plug-in.

Following a couple of service requests we've noticed that there is a slight difference how DataType is represented. In the older opcua-python library this is represended as, for example 'String' or 'Double'. The asyncio version seems to display DataType as an ENUM for example i=12 (string). Is there a reason for this change and is it possible to enforce a particular format for DataType as it seems to prevent some clients from connecting?

Many thanks!

schroeder- commented 2 years ago

There isn't any change, DataTypes are always NodeIds, there are no Enums behind it. Maybe you have a minimal example?

om327 commented 2 years ago

Hi, thanks for getting back to me!

I've fired up the minimal example for python-opcua and connected using the OPC UA client in SystemLink. It shows the DataType as Double and allows me to create a monitored item.

image

Double is also returned when using the Open2541 library.

The same minimal example in asyncua and DataType appears as i=11 and can not be used by the client to create a monitored item?

image

National Instruments believe there is something slightly different between the two impliementations that is causing this?

Thanks again!

om327 commented 2 years ago

I've tried to use the same functions and syntax for both implimentations but i'm still getting the same DataType difference when using the asyncio vs pyton-opcua (DataType i=11 rather than DataType=Double)

Interestingly in UaExpert it doesn't seem to make a difference and in both instances DataType is shown as Double.

python-opcua

import time
from opcua import ua, Server

if __name__ == "__main__":

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

    uri = "http://examples.freeopcua.github.io"
    idx = server.register_namespace(uri)

    myobj = server.nodes.objects.add_object(idx, "MyObject")
    myvar = myobj.add_variable(idx, "MyVariable", 6.7)
    myvar.set_writable()

    server.start()

    try:
        count = 0
        while True:
            time.sleep(1)
            count += 0.1
            myvar.set_value(count)
    finally:
        server.stop()

opcua-ascyncio

import asyncio
from asyncua import ua, Server

async def main():
    server = Server()
    await server.init()
    server.set_endpoint('opc.tcp://0.0.0.0:4840/freeopcua/server/')

    uri = 'http://examples.freeopcua.github.io'
    idx = await server.register_namespace(uri)

    myobj = await server.nodes.objects.add_object(idx, 'MyObject')
    myvar = await myobj.add_variable(idx, 'MyVariable', 6.7)
    await myvar.set_writable()
    async with server:
        while True:
            await asyncio.sleep(1)
            new_val = await myvar.get_value() + 0.1
            await myvar.write_value(new_val)

if __name__ == '__main__':
    asyncio.run(main())
oroulet commented 2 years ago

what is you see in the UI does not mean anything i=11 and Double are the same datatype. But it is a bit strange that what is shown in UI is differnet between python-opcua and opcua-asyncio. Something is confusing them. Maybe if you monitor the network with datashark you can find a difference when readong a simple double variable? maybe we are not encoding the nodeid the same way...

schroeder- commented 2 years ago

If compared the differences between python-opcua and asyncua and found the following: image image

I will make a fix to address the difference.

oroulet commented 2 years ago

if that is true, there is something strange. DataTypeDefinition should only be set for ObjectTypes. No idea where the bug is but this must somewhere very deep

schroeder- commented 2 years ago

The default is an empty ExtensionObject. We must return a error AttributeIdInvalid if the ExtensionObject is empty. This change triggers some errors in some other code that does expect empty ExtensionObjects instead of StatusCode errors...

oroulet commented 2 years ago

OK looks like your screenshot is for DataType under Types/DataTypes. Great you are looking at it. Did you check the spec for the correct behaviour?

schroeder- commented 2 years ago

There is nothing in the spec about empty DataTypeDefinitions. Found the following: node-ua same as asyncua open62541 same as python-opcua

I think we should return an error to keep some clients happy like UAExpert and maybe NI.

schroeder- commented 2 years ago

@om327 can you test it with fix #937 ? If it works we can merge it, if it doesn't help I will do some more research about the spec.

oroulet commented 2 years ago

@schroeder- Aren't the clients more happy with None instead of error? I do not remember if we can set an ExtensionObject to None, but we probably can. Maybe that is what python-opcua does?

oroulet commented 2 years ago

we shoul check what prosys does. They are usually very good at following spec. OK I checked they do: image

I may not have the latest server from them though...

schroeder- commented 2 years ago

Ok DataTypeDefinition is optional as of https://reference.opcfoundation.org/v104/Core/docs/Part3/5.8.3/. So return BadAttributeIdInvalid is ok if it doesn't exists or is empty ExtensionObject.

om327 commented 2 years ago

More than happy to test with NI SystemLink OPC UA - thanks for looking into this!

How do i download the latest changes before they are merged?

om327 commented 2 years ago

How do i use the command gh pr checkout 937 to checkout the new code?

Does this need to part of a git fetch origin statement?

Sorry my knowledge of git is little vague beyond cloning, pulling and pushing!

oroulet commented 2 years ago

Ok DataTypeDefinition is optional as of https://reference.opcfoundation.org/v104/Core/docs/Part3/5.8.3/. So return BadAttributeIdInvalid is ok if it doesn't exists or is empty ExtensionObject.

BadAttributeInvalid is fine if attribute is invalid. but I am wondering if creating empty ExtensionObject is a good idea at all in any places... maybe the default should be None instead of empty EntensionObject. Extension objects seems to be nullable: https://reference.opcfoundation.org/v105/Core/docs/Part6/5.1.2/

In that particular case the attribute should probably do not exist for these typoes apart for structs and enums

om327 commented 2 years ago

Ah found it - if in doubt just read the manual!!!

Just waiting for NI to update our license and then will be able to test out! If you want to have two versions of the attribute i can test both to see if they are both compatable?

emiliodiritaRA commented 2 years ago

In that particular case the attribute should probably do not exist for these typoes apart for structs and enums

I agree on this.

I recently had a similar problem, and I think it may be related to this: https://github.com/FreeOpcUa/opcua-asyncio/issues/1082

In fact, the existence of the attribute (even if it returns BadAttributeIdInvalid), I think is incorrect. The attribute is mandatory for structure or Enumeration datatypes, so I think it is optional "for other DataTypes". Finding it on types, as it happens now, I think is wrong.

emiliodiritaRA commented 1 year ago

Any update on this?

schroeder- commented 1 year ago

I quickly checked against a server with Softing SDK and I found we are missing a reference. image. This has to be fixed in the schema generator. Also in OPC UA there is nothing like a list of available attributes. The client decides which attributes to read, based on the nodetype and references. The server can only return BadAttributeInvalid or a concret value.

adriantom commented 1 year ago

How's the work going? Any progress?