Creating an ARDI Live Driver
This tutorial will re-create the OSI PI driver as an example of how to create your own ARDI driver in .NET
The Sketch
To begin, create a new console project in Visual Studio.
In your Program.cs, you need to define two classes.
- program.cs
//ARDI Driver SDK using ARDIDriverFramework; //PI .NET SDK using OSIsoft.AF; using OSIsoft.AF.Asset; namespace PILiveDriver { class Program { class PILiveDriver : ARDILiveDriver { public override ARDICore CreateCore() { return new PILiveConnection(); } } class PILiveConnection : ARDILiveDriverCore { public override bool Connect() { return false; } public override bool Disconnect() { return false; } public override bool Poll() { return false; } } static void Main(string[] args) { PILiveDriver Driver = new PILiveDriver(); Driver.Run(args); } } }
This is the basic layout of every ARDI .NET driver.
ARDILiveData
Your class has access to some important functions and variables.
NewData(string Key, string Value)
This function is responsible for queuing live data to be sent to ARDI (the data isn't physically sent until the poll function completes).
Parameter | Purpose |
---|---|
Key | The address of the data point that has been read |
Value | The value read from your data source |
Log(int Level, string Message)
Logs information back to the debug log for the driver.
Dictionary<string, List<PointInfo>> Addresses
This member contains a list of all of the data points the driver needs to load.
Each point contains the following information…
Member | Description |
---|---|
address | The address (or lookup value) for the point. |
code | The ARDI code for this point, in asset id:property id:attribute format. |
While there are other attributes of the point, these are used internally and you should not need to access them.
Functions
There are five key functions required in your driver class.
Connect
The connect function is called whenever your driver is to connect to a data source. It is passed the driver address (which is often a colon-delimited string).
It returns True if connection was successful and False if connection fails.
Disconnect
This does the opposite of connect - it closes the connection to your data source and does any cleanup required.
Optimise
The Optimise function is called when the driver has successfully connected, but before the first call to poll.
The function is also called whenever a change occurs to the data links in ARDI that would effect your driver (ie. when a new asset is linked to data from your source).
The code in this function should perform any work that needs to be done to streamline the work of the poll function in getting data from the source.
For example, if this was an SQL data source, it would probably create your SQL statement here.
Poll
The poll function is called in a constant loop every time ARDI would like additional data from the data source (the rate this occurs is the refresh rate of the driver configured in the ARDI web interface).
In this function you perform the actual request for new data (or if your data requests are asynchronous, deliver your most recent data).
Implementation
Connect
First, we need do read in the address we are supposed to be connecting to.
The address for our text file driver will include the following options…
Option | Description |
---|---|
Server | The name of the PI AF Server |
Database | The name of the specific AF database |
Username | Optional - the username to login as |
Password | Optional - the password to login with |
The address lists these options, separated by a colon (:) character. IE. \\MYPISERVER:MyAssets::.
The first steps of the Connect function are to…
- Split the address string into its component parts
- Record this information to internal members
string[] pieces = Address.Split(':'); string Server = ""; string DBName = ""; string Username = ""; string Password = ""; if (pieces.Length < 4) return false; Server = pieces[0]; DBName = pieces[1]; Username = pieces[2]; Password = pieces[3];
Connecting
Next, we establish a connection with the data source. In this case, we'd like to need to know a few things….
- How we are connected to PI (ConnectedSystem)
- A link to the specific PI database we want to work with (DB)
- A list of 'AF Attributes' (individual data points) we want to keep an eye on (PollingSet)
//Get a list of all of the available PI systems. Log(1, "Connecting to PI Server"); PISystems SystemList = null; try { SystemList = new PISystems(); } catch(Exception e) { Log(0,"Couldn't get list of PI systems - " + e.Message); return false; } //Find the server we have requested. try { ConnectedSystem = SystemList[Server]; if (Username != "") { Log(0, "Authenticating With PI system for user " + Username); ConnectedSystem.Connect(new NetworkCredential(Username, Password)); } } catch(Exception e) { Log(0, "Couldn't find named PI system '" + pieces[0] + "' " + e.Message); return false; } //Exit if we couldn't get one. if(ConnectedSystem == null) { Log(0, "PI System '" + Server + "' not found."); return false; } //Grab a reference to the database itself. try { DB = ConnectedSystem.Databases[DBName]; } catch { Log(0, "PI Database'" + DBName + "' not found."); return false; }
Finally, we need to get a list of the attributes we are interested in monitoring. To do this, we loop through each of our addresses, clean up the names slightly (in case the user used the incorrect backslash character), and ask PI for the matching attribute.
foreach (KeyValuePair<string, List<PointInfo>> KV in Addresses) { FullPath = KV.Key.Replace('/', '\\'); try { AFAttribute Attr = new AFAttribute(DB, FullPath); PollingSet.Add(Attr); } catch { Log(1, "Unable to located PI AF address '" + FullPath + "'"); } continue; }
Polling
Now that we have a list of the points we want to watch, we can fill in our poll function.
Poll is repeatedly called to ensure that the live values we are working with are running as expected.
If the data we fetch is bad, return the “^” symbol (which ARDI interprets as 'Bad Data').
//Fetch a list of fresh values from PI Asset Framework AFValues valueset = PollingSet.GetValue(); foreach(AFValue V in valueset) { //Figure out the value for this point. if (!V.IsGood) { val = "^"; { else { try { Type Tx = V.ValueType; try { if (Tx.Name == "AFEnumerationValue") { val = V.ValueAsInt32().ToString(); } } catch { val = V.Value.ToString(); } if (val == "") val = V.Value.ToString(); } catch(Exception e) { val = "^"; } } //Write the data out (if required), based on the PI address. NewData(V.Attribute.GetPath(), val); }
Creating a Web Interface
The last step is to create a web interface for our driver, so that we can set the drivers various options (file name, delimiter, column numbers etc.) in a friendly manner.
This will require some basic HTML & PHP.
Firstly, copy one of the existing drivers user interfaces by copying /opt/ardi/web/data/live/pi to /opt/ardi/web/data/live/pi (the folder name must match the name of the driver folder & file).
There are several PHP files in this folder. Click on them for examples and documentation on what they do.
File | Purpose |
---|---|
info.inc | Provides details on the driver |
configure-source.php | The user interface for setting up the data source |
saveconfig-source.php | Convert the properties set in configure-source.php to a machine-readable address string |
friendlyname.php | Convert the address from saveconfig-source.php to a human-readable text string |
link.php | The user interface for setting up a data link between the source and an asset property |
encode.php | Convert the properties set in link.php to a machine-readable address string |
decode.php | Convert the address from encode.php to a human-readable description |