FreeOpcUa / python-opcua

LGPL Pure Python OPC-UA Client and Server
http://freeopcua.github.io/
GNU Lesser General Public License v3.0
1.36k stars 658 forks source link

Kepware Bad Quality #1004

Open AndrewStuhr opened 4 years ago

AndrewStuhr commented 4 years ago

I'm trying to read multiple tags from Kepware in a single request. If one or more tags has bad quality, the entire read fails. Is there a way to get a bad quality status back for bad tags without the entire read failing? Here's part of my code where I'm reading multiple tags at once:

for node in nodeList: nodeid = ua.NodeId.from_string(node) attr = ua.ReadValueId() attr.NodeId = nodeid attr.AttributeId = ua.AttributeIds.Value params.NodesToRead.append(attr) result = client.uaclient.read(params)

Thanks

AndreasHeine commented 4 years ago

what do you mean with failing is it a python error or a opcua statuscode?

AndrewStuhr commented 4 years ago

It's an opcua status code : bad 0x80000000.

It takes about 3-4 seconds to from when the read request begins until opcua generates this statuscode. I'm looking for a way to read multiple tags every second and get a bad quality response just for bad quality tags, yet still get tag values for good quality tags. Any thoughts?

Thanks again!

AndreasHeine commented 4 years ago

sounds impossible... one read request and multiple responses... if it would be an python server i would use a opcua-method or if the datatypes are equal just a variable with n dimensions... how many variables u plan to read?

AndrewStuhr commented 4 years ago

I worded that poorly... I'm looking to get one response back with an array/object that contains read values for each node. However, if a node had bad quality, the array/object would have some indication of the bad quality instead of the node's value.

Currently I was just reading a couple of nodes, but eventually I'm hoping to read a lot - around 1000 per second.

AndreasHeine commented 4 years ago

ive read 2048 boolean io from a s7-1500 but it was only possible as an array (variable type boolean with arraydimension of 2048)!

but to be honest with you ive never worked with kepware ive wrote the services by myself...

what i wonder atm is why a node could have bad quality?

AndrewStuhr commented 4 years ago

In this case, it's because Kepware is reading data from scale that may or may not have an object on it. If there isn't an object on the scale, it responds to Kepware in a way that makes the Kepware quality go bad. Once an item is being weighed, the quality in Kepware temporarily becomes good.

Do you think the current method I'm using to read multiple variables will be unable to handle 1000 nodes, even with all good quality?

AndreasHeine commented 4 years ago

the thing is, it depends on the server hardware, network traffic, and a bunch of more things like opcua server config... u can read 1000 nodes for sure but in 1s interval...

python-opcua/opcua/client/ua_client.py:

    def read(self, parameters):
        self.logger.info("read")
        request = ua.ReadRequest()
        request.Parameters = parameters
        data = self._uasocket.send_request(request)
        response = struct_from_binary(ua.ReadResponse, data)
        self.logger.debug(response)
        response.ResponseHeader.ServiceResult.check()
        # cast to Enum attributes that need to
        for idx, rv in enumerate(parameters.NodesToRead):
            if rv.AttributeId == ua.AttributeIds.NodeClass:
                dv = response.Results[idx]
                if dv.StatusCode.is_good():
                    dv.Value.Value = ua.NodeClass(dv.Value.Value)
            elif rv.AttributeId == ua.AttributeIds.ValueRank:
                dv = response.Results[idx]
                if dv.StatusCode.is_good() and dv.Value.Value in (-3, -2, -1, 0, 1, 2, 3, 4):
                    dv.Value.Value = ua.ValueRank(dv.Value.Value)
        return response.Results

i assume your code looks like this:

for node in nodeList:
    nodeid = ua.NodeId.from_string(node)
    attr = ua.ReadValueId()
    attr.NodeId = nodeid
    attr.AttributeId = ua.AttributeIds.Value
    params.NodesToRead.append(attr)
result = client.uaclient.read(params)

if the nodelist does not change at runtime: the first thing i would try is to run the forloop just once an store "params", so you gain some speed (1000 nodes => 1000 iterations to get params + 1000 iterations in the for loop of the read-method => 2000 iteration total ...) the next thing is to try multiprocessing/multithreading with more then one opcua-client-connection so u can split up work depends on your hardware...

but all that does not help with the bad quality in kepware... :(

AndrewStuhr commented 4 years ago

Thanks for the performance tips. I'll try these out.

I was thinking I could modify the opcua library to handle the bad status code differently, but I don't think that would even help because it take's 3-4 seconds to just get the response. Let me know if you have any other thoughts come to mind! Thanks

zerox1212 commented 4 years ago

This sounds like a bug. I do not think an entire read request should fail due to one node having bad status.

However, it will be many times faster to subscribe to 1000 items than do an explicit read. Not to mention subscribing to 1000 nodes will solve your bad quality issue because one bad node won't stop the subscription from updating all the other ones in the subscription.

AndrewStuhr commented 4 years ago

Damn, I haven't even looked into subscriptions. I've just been reading on an interval. I'll definitely try subscriptions, thank!

AndreasHeine commented 4 years ago

@zerox1212: is the subscription on servers with encryption still broken or fixed? (OPC UA Server: AttributeError: 'NoneType' object has no attribute 'encrypted_block_size' #956)

zerox1212 commented 4 years ago

I don't know. I never used encryption when I used this library.

AndreasHeine commented 4 years ago
from opcua import Client

nodeList = ["ns=0;i=2267","ns=2;i=2"]

class SubHandler(object):

    """
    Client to subscription. It will receive events from server
    """

    def datachange_notification(self, node, val, data):
        print("Python: New data change event", node, val)

    def event_notification(self, event):
        print("Python: New event", event)

if __name__ == "__main__":
    client = Client("opc.tcp://127.0.0.1:4840")
    client.connect()
    handler = SubHandler()
    sub = client.create_subscription(500, handler)
    for node in nodeList:
        handleList = sub.subscribe_data_change(client.get_node(node))
AndrewStuhr commented 4 years ago

Thanks for the code Andreas! I'm able to set up a subscription, and it's handling bad quality tags (it just returns 'None').

One final question: I'm running your script in Window's command prompt. Is there a way to exit the script? Currently, CTL + C isn't doing anything. Is there something I have to do with client.disconnect()?

Thanks again

AndreasHeine commented 4 years ago

try: while 1: time.sleep(0.5) except KeyboardInterrupt: unsubscribe loop disconnect

sorry wrote it on my phone...

AndrewStuhr commented 4 years ago

I used the following code and now I can stop the script with CTL + C - thanks.

The only issue is I get this duplicate output message: "ServiceFault from server received while waiting for publish response" (twice)

I believe these messages are print statements and not errors because there's no traceback, literally just this text. Do you see anything I should change to prevent these messages?

if __name__ == "__main__":
    client = Client("opc.tcp://localhost:49320")
    client.connect()

    handler = SubHandler()
    sub = client.create_subscription(500, handler)
    handleList = []
    for node in nodeList:
            handleList.append(sub.subscribe_data_change(client.get_node(node)))

    try:
        while 1:
            time.sleep(0.5)

    except KeyboardInterrupt:
        for handle in handleList:
            sub.unsubscribe(handle)

        sub.delete()
        client.disconnect()
AndreasHeine commented 4 years ago

I actually had the same issue with the double printed it appearce that there is a bug... but not with pub/sub i got it with opcua-method-call... till now i could not find anything...

AndrewStuhr commented 4 years ago

Interesting... please let me know if you figure it out. And thanks again, you rock man!