=====Creating an ARDI Event Driver===== This tutorial will re-create the ARDI LogFile Event driver as an example of how to create your own ARDI driver in Python. Many of the steps are very similar to those from our [[creating_a_live_ardi_driver|Creating an ARDI Live Driver]] tutorial - however the functions that we create are a little different. ====The Sketch==== To begin, we will sketch out the basic structure of our Python script. First, create a new folder in **/opt/ardi/drivers/event** with the name of your driver (ie. **/opt/ardi/drivers/event/logfile2**. Now create a matching python source file - a plain text file - in that folder, named //.py// - ie **/opt/ardi/drivers/event/logfile2/logfile2.py**. Paste in the following content... from ardi.driver import eventdriverbase class MyDriver: def __init__(self): self.connected = False def SetAddress(self,addr): pass def Connect(self): return True def Disconnect(self): pass def RunQuery(self, query): return query.Finish() class driverfactory: def createinstance(self): return MyDriver() if __name__ == "__main__": sdf = driverfactory() base = eventdriverbase.eventdriver() base.start(sdf) This is the basic layout of every ARDI Python historical driver. It's almost identical to that of the live driver, however in this case we don't have the **Optimise** or **Poll** functions, replacing them instead with **Query**. ====Core==== Your class has access to some important functions and variables. ===Query.AddLineFromDictionary()=== This function is responsible for outputting an event that is then sent to ARDI. ^Parameter^Purpose^ |Dictionary|A dictionary describing the event. At a minimum, it should include the keys **start** and **name**.| Data from the dictionary is used to create an Event. Each event needs - as an absolute minimum - a **start** element (containing the UTC date/time of the event) and a **name** element (used to identify the event). They may also include **end** or **duration** properties. //End// is the UTC date/time when the event is complete, and //Duration// is the number of seconds the event lasts for. Note that if both //end// and //duration// are given, the 'end' time is given priority. Any other properties you include are displayed as **metadata** for the event. For example, you could include status information, averages or totals for key events. These other elements are not translated (ie. any dates/times given in these properties will **not** be translated into UTC). ===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**. ====Functions=== There are four 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. ===Query=== The **Query** function is used to actually perform a query on the event source. It includes a single python object that contains all of the information you need to run your query. ^Attribute^Description^ |start|The start date for the query| |end|The end date for the query (may be the same as above)| |filter|Text to search/filter for. If blank, no filter should be applied.| =====Implementation===== ====Splitting Our Address==== The //address// for our text file driver will include the following options... ^Option^Description^ |File Name|The name of the text file to open| |Delimiter|The delimiting character. If blank, it defaults to TAB (/t)| |Name Column|The number of the column containing the event description| |Start Column|The number of the column containing the start time| |End Column|The number of the column containing the end time| |Date Format|The format to use to decode Date/Time values| The address lists these options, separated by a colon (:) character. IE. **/tmp/data.txt:,:1:2:%Y-%m-%d %H:%M:%S**. It's up to this function to... * Split the address string into its component parts * Record this information for use in the **connect** and **query** functions bits = addr.split(':') self.filename = bits[0] self.delimiter = bits[1] if self.delimiter == "\\t": self.delimiter = "\t" self.namecol = bits[2] self.startcol = bits[3] self.dateformat = bits[4] ====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 ====RunQuery==== This is where the magic happens. This function is responsible for reading the data source and returning time-stamped values for the points that have been requested. #Begin the Query self.core.logger.info("Query Started: Between " + query.start + " and " + query.end) #Open and process the file... with open(self.filename,'r') as f: for line in f: #Split each line by the delimiter... bits = line.split(self.delimiter) #Is this date in the range? startdate = bits[self.startcol] TT = datetime.datetime.strptime( startdate, self.dateformat ) if TT < query.start: lastvalue[bits[self.namecol]] = str(bits[self.valuecol]).strip() continue if TT > query.end: #This is AFTER the date range - break out of the loop. break #Add this value to the data to be transmitted. data = {} #Name and Start are required... data['name'] = bits[self.namecol] data['start'] = bits[self.startcol] #We can throw in some additional stuff here, if we want data['origin'] = bits[0] #Write this out query.AddLineFromDictionary(data) #That's it - lets output our results. return query.Finish() ====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/event/logfile** to **/opt/ardi/web/data/event/logfile2** (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|info.inc]]|Provides details on the driver| |[[configure-source_php|configure-source.php]]|The user interface for setting up the data source| |[[saveconfig-source_php|saveconfig-source.php]]|Convert the properties set in //configure-source.php// to a machine-readable address string| |[[friendlyname_php|friendlyname.php]]|Convert the address from //saveconfig-source.php// to a human-readable text string|