weixin_39935903
weixin_39935903
2020-12-02 14:11

asyncio compatibility

Does pylogix have any asyncio compatibility? Not sure if this is a dumb question since I'm just learning about asyncio. Other libraries such as freeOpcUa have a specific library for asyncio support (opcua-asyncio).

I'm looking into asyncio to speed up tag reads. I'm reading individual tags from different ip addresses once a second. There is no guarantee the tags are in an array, which I know is one way to speed up reads. I'm taking advantage of reading mulitple tags at once, but there seems to be a limit on how many tags the function can handle.

Just wondering if I'd need to modify the library to add asyncio support. Let me know if you have any thoughts! Thanks

该提问来源于开源项目:dmroeder/pylogix

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答

13条回答

  • weixin_39589557 weixin_39589557 5月前

    Imo concurrency will bring an extra level of complexity to the project. I think it should be handled at the application level not library level.

    Pylogix supports py2 and py3. Asyncio is 3.4 and above. Which means it would be hard to maintain in the library.

    点赞 评论 复制链接分享
  • weixin_39935903 weixin_39935903 5月前

    Ok, just got it to work, and it was much easier than I expected! Here's my example for anyone else whose new to asyncio (like me). All you'll need to change is the ip, slot, and tag names. The printout shows "start" before the values are returned.

    
    import asyncio
    from pylogix    import PLC
    
    comm = PLC()
    comm.IPAddress = '192.168.50.29'
    comm.ProcessorSlot = 0
    tagList = ['DAQ_Sinewave', 'DAQ_SineWave_Fast_Real', 'DAQ_Bool2']
    connectTimout = 1
    
    async def readTag(tag):
        try:
            print("start")
            x = await asyncio.wait_for(read(tag), timeout=connectTimout )
            print(x.value)
    
        except Exception as e:
            print(str(e))
    
    async def read(tag):
        return comm.Read(tag)
    
    
    async def main():
        tasks = []
        for tag in tagList:
            tasks.append(asyncio.create_task(readTag(tag)))
    
        await asyncio.gather(*tasks) #Wait for all tasks to complete
    
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
    
    点赞 评论 复制链接分享
  • weixin_39589557 weixin_39589557 5月前

    Nice! Thanks for sharing the example.

    点赞 评论 复制链接分享
  • weixin_39935903 weixin_39935903 5月前

    Took forever to format it as a comment haha but I think its good now

    点赞 评论 复制链接分享
  • weixin_39589557 weixin_39589557 5月前

    I've fixed the coding format. If you add triple back single quotes in the beginning and end of code it will format properly for you.

    点赞 评论 复制链接分享
  • weixin_39844481 weixin_39844481 5月前

    so I take it when you say you are reading once a second, you chose this particular interval because of the number of tags you are trying to read takes about that much time to accomplish? In other words, you have lot of tags to read, you have set the one second interval because of the time it takes to read them and you would like to do it more frequently.

    Threading is one way, make parallel connections, obviously that adds some level of complication. Another way to speed things up a bit is to provide the data type. This helps even more when reading lists of tags. For example:

    tags = [('tag1', 196), ('tag2', 196), ('tag3', 194)]

    where tag1 and tag2 are a DINT, tag3 is a SINT (you can get the values from self.CIPTypes)

    So when you provide the data type, pylogix won't have to retrieve the data type of each new tag. There is obvious room for improvement here, most people in the PLC world refer to the data type DINT as a DINT rather than 196 (or 0xc4). I have a balance I have to strike between CS types and controls types....

    点赞 评论 复制链接分享
  • weixin_39935903 weixin_39935903 5月前

    That's pretty much correct. I've seen it take around .4 seconds to read about 100 tags previously, so I chose 1 second to give some padding if we log more tags.

    Thanks for the tip to pass in the data type. I tried this out and got the error: 'int' object has no attribute 'split'. I then passed in the numbers as strings ('196' instead of 196). This worked, but actually slowed it down. Any idea what I'm doing wrong? Here's my code:

    import asyncio
    import time
    from pylogix    import PLC
    
    comm = PLC()
    comm.IPAddress      = '192.168.50.29'
    comm.ProcessorSlot  = 0
    # tagList           = ['DAQ_Sinewave', 'DAQ_SineWave_Fast_Real', 'DAQ_Bool2']
    tagList             = [('DAQ_Sinewave',196), ('DAQ_SineWave_Fast_Real',202), ('DAQ_Bool2', 193)]
    connectTimout       = 1
    start = time.time()
    
    async def read(tag):
        return comm.Read(tag)
    
    
    async def readTag(tag):
        try:
            x = await asyncio.wait_for(read(tag), timeout=connectTimout)
    
        except Exception as e:
            print(str(e))
    
    
    async def main():
        tasks = []
        for tag in tagList:
            tasks.append(asyncio.create_task(readTag(tag)))
    
        start_time = time.time()
        await asyncio.gather(*tasks)
        elapsed_time = time.time() - start_time 
        print("async elapsed time : " + str(elapsed_time))
    
    
    
    #Async read
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
    
    
    #Normal Read
    start_time = time.time()
    for tag in tagList:
        comm.Read(tag)
    elapsed_time = time.time() - start_time 
    print("normal elapsed time : " + str(elapsed_time))
    
    
    
    点赞 评论 复制链接分享
  • weixin_39844481 weixin_39844481 5月前

    I'm not sure I have a reason why either situation you described makes sense. The data type should be passed as integers and there is no reason why it would slow down.

    Make sure you are running the latest (0.6.2), write two simple scripts without the async stuff, one reading a few tags without the data type, one with the data type, compare the result.

    点赞 评论 复制链接分享
  • weixin_39844481 weixin_39844481 5月前

    I just noticed in your script that you are probably using pylogix from a fork rather than the main project.

    Do:

    
    import pylogix
    pylogix.__version__
    

    I suspect it will say 0.5.x, which is not this project. With the exception of 0.5.0, I skipped that number series for this reason, once I noticed that people were installing the fork.

    点赞 评论 复制链接分享
  • weixin_39935903 weixin_39935903 5月前

    Just saw this, thank! I had version 0.5.1. Just curious, what part of my code indicated my version was old?

    点赞 评论 复制链接分享
  • weixin_39844481 weixin_39844481 5月前

    I missed it at first, but the line that gave it away was:

    print(x.value)

    Specifically, the fact that "value" was lower case. See, when I switched to returning the Response class, I accidentally pushed out changes where the class members were lower case. I considered changing all of the class members to lower case at that time, but decided not to as it would be a much more drastic change for current users. I never intended on that commit going out that way.

    Before I realized it, a user forked my project at that point, then put it up on pypi. It took me a while to realize it, many users were contacting me about why their projects were crashing. They were using an outdated fork that was up on pypi because they installed via pip. So when people either say "your examples say ret.Value but my code worked with ret.value", I know they were probably using that fork.

    The pypi problem has been recently resolved, so when you install via pip, you are in fact installing this project.

    点赞 评论 复制链接分享
  • weixin_39935903 weixin_39935903 5月前

    Gotcha, that makes a lot of sense. I've ran into that value/Value issue a few times. Glad pypi get's the most recent version now!

    点赞 评论 复制链接分享
  • weixin_39844481 weixin_39844481 5月前

    Where are you at on your performance issue when passing the data type? Is it still performing the same now that you've updated? I assume that the values are now passed as integers rather than strings?

    点赞 评论 复制链接分享

相关推荐