Hello everybody! Today I'm going to talk about my favorite programming language: Python. More specifically, how to integrate Python and our beloved AF SDK in order to extract data from PI. PI and Python? Let's call it PIthon.

Some of you may be wondering why would one do that. (If you are not, jump to the next paragraph). Well, Python has a very active community and is a well established programming language. It's multi-platform and has an uncountable number of libraries and tools. It's also heavily used for big data analytics when used together with libraries like SciPy and NumPy.

First, let me explain why I'm not using IronPython. If you don't know what it is, let me get its description: "IronPython is an implementation of the Python programming language targeting the .NET Framework and Mono.". See the problem? It's an implementation (C# if you are wondering) and not pure Python, so it frequently has problems integrating with third-party libraries. Also, it targets Mono (i.e Linux) and currently AF SDK does not support it. Last but not least, the latest stable release was almost two years ago, so things may be very outdated.

So let's start. For this tutorial you will need:

- A Windows box

- Python 2.7

- AF SDK (Included in the AF Client installer)

- Pythonnet

Proceed with the installation of everything and once it's done, go ahead and create a module called PIthon. Now comes the most important part: libraries import.

import sys
sys.path.append('C:\\Program Files (x86)\\PIPC\\AF\\PublicAssemblies\\4.0\\')
import clr
clr.AddReference('OSIsoft.AFSDK')

And that's all. No kidding. It's so simple I don't dare to explain it. The important thing is that from here you can use a syntax that resembles a lot C# development. Start by importing classes:

from OSIsoft.AF.PI import *
from OSIsoft.AF.Search import *
from OSIsoft.AF.Asset import *
from OSIsoft.AF.Data import *
from OSIsoft.AF.Time import *

Now let's create two important functions.

def connect_to_Server(serverName):
    piServers = PIServers()
    global piServer
    piServer = piServers[serverName]
    piServer.Connect(False)

def get_tag_snapshot(tagname):
    tag = PIPoint.FindPIPoint(piServer, tagname)
    lastData = tag.Snapshot()
    return lastData.Value, lastData.Timestamp

Did you notice how close to C# development this is? Most of the time is pretty much the same, but let me give you a very important tip : PythonNet does not handle well optional parameters from .NET assemblies, so you always need to be explicit. That's why I had to pass False on piServer.Connect. The explicit parameter allows me to dodge the optional parameter problem.

Well, from here you have your module, so let's use it! Go ahead and create a new script and use PIthon:

from PIthon import PIthon

PIConnector.connect_to_Server("Server")
value, timestamp = PIConnector.get_tag_snapshot('sinusoid')
print('Timestamp: {0} Value: {1}'.format(timestamp, value))

And if you run this...

Timestamp: 8/1/2016 5:03:59 PM Value: 73.48035

Voilà! See how simple it was? From here you have the base to create your own AF SDK module for Python! And if you have any questions, feel free to ask them!

Have fun coding with PIthon!

  • Hi ***,

     

    The pythonet framework doesn't handle optional parameters very well so you have to explicitly call it as None. In your case, you are calling the method using this signature:

    FindPIPoints(PIServer piServer, IList<IEnumerable<PIPointQuery>> queries, IEnumerable<String> attributeNames = null)

     

    See it has an optional parameter at the end? Because of it, you should use it in python like this: 

     

    tags = PIPoint.FindPIPoints(piServer, ["sinusoid","sinusoidu"], None)

     

    A couple of months ago I also described how to use FindPIPoints on the comment here and I strongly suggest you read it because it allows you to use masks. In your case, something like find_tags('sinusoid*').

     

    Cheers!

  • Hi Rafael,

    Thanks so much for your post! 

    Have you tried to load a list of PI Tag names and create PIPoints object directly? The code below doesn't work. The only workaround I find is to write a loop. Not sure if you have a better solution:


    pastedImage_1.png_e09349dd-3769-41a0-969e-4bcf26a9cf78.png

  • Hi, Rafael Borges

    Thanks a lot.

    This helped me!

    I spent whole day to do it last time. But it turns out so easy!

    Thank you!

    Cheers!

  • I don't know the error you are getting, but I believe I know where the problem is (actually, there are two: on the line where you do "piServers.DefaultPIServer", you end it with a semicolon).

     

    The problem, I believe, is that you are calling pt.LoadAttributes() with no arguments. Pythonet is not able to convert a params attribute into an optional attribute, so you have to call the method explicitly with a None parameter:

     

    >>> pt=PIPoint.FindPIPoint(piServer, "SINUSOID")

     

    >>> pt.LoadAttributes()

     

    Traceback (most recent call last):

     

    File "<stdin>", line 1, in <module>

     

    TypeError: No method matches given arguments for LoadAttributes

     

    >>> pt.LoadAttributes(None)

     

    >>> print(pt.GetAttribute('Descriptor'))

     

    12 Hour Sine Wave

     

     

    I hope this helps!

    Cheers!

  • Hello, Rafael

     

    i'm beginner for python and AF SDK developer

     

    Could you help me please?

     

    i want to get description from attribute, but i always have the errors

     

    this is my code:

     

    import sys

     

    import clr

    sys.path.append(r'C:&#92;Program Files (x86)\PIPC\AF\PublicAssemblies\4.0')

     

    clr.AddReference('OSIsoft.AFSDK')

    from OSIsoft.AF import *

     

    from OSIsoft.AF.PI import *

     

    from OSIsoft.AF.Asset import *

     

    from OSIsoft.AF.Data import *

     

    from OSIsoft.AF.Time import *

     

    from OSIsoft.AF.UnitsOfMeasure import *

     

    # PI Data Archive

     

    piServers = PIServers()

     

    piServer = piServers.DefaultPIServer;

    pt=PIPoint.FindPIPoint(piServer, "TEST_ENGY.GRU06.3SHU.Y053.G.M.0001")

     

    pt.LoadAttributes()

    print(pt.GetAttribute('Descriptor'))