Creating an ARDI Live Driver

This tutorial will re-create the ARDI Live Text driver as an example of how to create your own ARDI driver in Python.

The Sketch

To begin, we will sketch out the basic structure of our Python script.

First, create a new folder in /opt/ardi/drivers/live with the name of your driver (ie. /opt/ardi/drivers/live/text2.

Now create a matching python source file - a plain text file - in that folder, named <foldername>.py - ie /opt/ardi/drivers/live/text2/text2.py.

Paste in the following content…

baselivedriver.py
from ardi.drivers import driverbase, drivercore
 
class MyDriver:
 
   def __init__(self):
      self.connected = False
      self.polling = True
 
   def SetAddress(self, addr):
      pass
 
   def Connect(self):
      return True
 
   def Disconnect(self):
      pass
 
   def Optimise(self):
      pass
 
   def Poll(self):
      pass
 
class driverfactory:
    def createinstance(self):
        return MyDriver()
 
if __name__ == "__main__":
    sdf = driverfactory()
    base = driverbase.ardidriver()
    base.start(sdf)
 

This is the basic layout of every ARDI Python driver.

Functions

There are five key functions required in your driver class.

SetAddress

This function is called after your driver is first created. It includes an encoded address for your data source from ARDI.

You'll need to split it into its component parts and store them for when its time to connect.

Connect

The connect function is called whenever your driver is to connect to a data source.

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

Your class has access to some important functions and variables.

self.core.NewData()

This function is responsible for queuing live data to be sent to ARDI (the data isn't physically sent until the poll function completes).

ParameterPurpose
AddressThe address of the data point that has been read
ValueThe value read from your data source

self.core.logger

This member is a Python logging object that can be used to record any information of note, through calls like self.core.logger.info and self.core.logger.debug.

self.core.points

This member contains a list of all of the data points the driver needs to load. It's a Python list containing an object for each point.

Each point contains the following information…

MemberDescription
addressThe address (or lookup value) for the point.
codeThe 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.

Splitting Our Address

The address for our text file driver will include the following options…

OptionDescription
File NameThe name of the text file to open
DelimiterThe delimiting character. If blank, it defaults to TAB (/t)
Lookup ColumnThe number of the column that contains the value name
Value ColumnThe number of the column that contains the value itself

The address lists these options, separated by a colon (:) character. IE. /tmp/data.txt::1:2.

It's up to this function to…

  • Split the address string into its component parts
  • Record this information for use in the connect, optimise and poll functions
        bits = addr.split(':')
        self.filename = bits[0]
        self.delimiter = bits[1]
        if self.delimiter == "\\t":
            self.delimiter = "\t"
        self.lookupcol = bits[2]
        self.valuecol = bits[3]

Connecting to a Text File

It's up to our connect function to…

  • Check that the file exists

Because we want to allow applications to change the file between polling it, we won't actually open the file at this point - we will open the file each time we poll it.

   if os.path.isfile(self.file):
      return True
 
   self.core.logger.warn("File not found - Data source could not be opened")
   return False

Optimising

There isn't a great deal of optimisation to do for this particular driver - we don't need to prepare an SQL query, subscribe to a web service etc.

Polling

To poll this file, we need to…

  • Open the file
  • Read it line by line, splitting by the delimiter
  • If the Lookup Column matches the address of any of our points, we need to give the point the value in our Value Column (this is done using the NewData function)
  • Close the file so that other processes can write to it.
   with open(self.file,'r') as f:
            for line in f:
                bits = line.split(self.delimiter)
 
                if bits[0] in self.lookup:
                    vl = str(bits[1]).strip()
                    self.core.NewData(bits[0],vl)

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/text to /opt/ardi/web/data/live/text2 (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.

FilePurpose
info.incProvides details on the driver
configure-source.phpThe user interface for setting up the data source
saveconfig-source.phpConvert the properties set in configure-source.php to a machine-readable address string
friendlyname.phpConvert the address from saveconfig-source.php to a human-readable text string
link.phpThe user interface for setting up a data link between the source and an asset property
encode.phpConvert the properties set in link.php to a machine-readable address string
decode.phpConvert the address from encode.php to a human-readable description