Indigo Web Server (IWS) Plugins
IWS Plugins add functionality to the Indigo Web Server. If you’re familiar with how web forms are processed then it should be pretty straight-forward to understand how to develop an IWS plugin. If you’re familiar with Python, then your job will be even easier. It’s important to note, however, that IWS Plugins that show HTML pages aren’t compatible with Indigo Touch - Touch doesn’t render control pages via HTML so if your plugin presents a UI, Indigo Touch will direct any request to your plugin to Safari rather than render your plugin’s UI within the Indigo Touch application.
An IWS Plugin can handle multiple request types (identified on the URL) by defining several handlers:
http://localhost:port/examplePlugin/handler1 http://localhost:port/examplePlugin/handler2 http://localhost:port/examplePlugin/handler3
and so on, and any handler can take parameters either via a POST or a GET:
http://localhost:port/examplePlugin/handler1?param1=p1val¶m2=p2val
So, how do you implement an IWS Plugin? An IWS Plugin requires a very specific layout of files/folders in it’s directory. Namely, something that looks like this:
A IWS plugin directory is installed into the following location to become active:
/Library/Application Support/Perceptive Automation/Indigo 5/IndigoWebServer/plugins/
The folders in this structure are as follows:
css
- store CSS files used in templates or static pages (see below for details on templates)images
- store image files in this directoryjs
- store javascript files in this directorystatic
- store any static HTML files that your plugin would like to serve. For instance, you might want an about HTML file that gets displayed when the user selects a help link.templates
- store template files here (see below for details on templates)
The init.py
file is unused, but it’s required - it can just be empty. The meat of the functionality is in the reqhandler.py
. The structure of this file goes something like this: imports, static hook methods, request handler class.
Imports are just the imports necessary for your plugin to work. These are standard Python import statements. Some common imports you’ll need are .
There are five optional static hook methods that get called by IWS for various purposes:
PlugInName()
- this method should return a string with a human readable name for your plugin. It will be used in the Event Log when the plugin loads and will (optionally) be shown on the IWS index (landing) page.PluginDescription()
- this method should return a string with a human readable description of what your plugin does. This will (optionally) be shown on the IWS index (landing) page. to the right of the name specified above.ShowOnControlPageList()
- this method should return true if you want your plugin listed on the IWS index page (using the name and description above). If you want your plugin to show up there, you’ll need to make sure you implement an index method in your handler class (described below) so that when the user clicks on the name of your plugin in the list the index page will load.IndigoConnected()
andIndigoDisconnected()
- these methods are called just when their name implies - when IWS connects and disconnects from the IndigoServer process. Use these methods if you need to initialize and/or deallocate resources of some type.
The last, and most important part of the code, is your request handler class. The methods defined in this class are automatically called based on the URL requested. The class itself may be named anything, but must be a subclass of BaseRequestHandler:
class ExampleRequestHandler(BaseRequestHandler):
The plugin request handler must subclass from BaseRequestHandler and must call the parent class init
method:
def __init__(self, logFunc): BaseRequestHandler.__init__(self, logFunc)
Most plugins will have an index method - this is what’s called when the plugin’s path doesn’t contain an actual handler:
http://localhost:port/examplePlugin/
The method should do something for the user - some plugins just call another handler with default values, others will just display a control page like UI, and still other will show a simple text page that tells the user how to use the plugin. This index method, for example, just calls another handler in the class:
def index(self): thePage = self.getReportForDay() return thePage index.exposed = True # exposed = True must be set for handler func to be called
You’ll notice that there’s an extra line after the definition of the index handler:
index.exposed = true
This tells IWS that this method is available as an externally called handler. You can define helper methods in your class that shouldn’t be called directly by the user - adding this line (handlername.exposed) will restrict user access to only those methods which you want the user to use.
That’s the basic structure of the reqhandler.py
file for all IWS plugins. You can have as many handlers as you like and they can process parameters just like any forms processing software. For instance, let’s look at the definition of the getReportForDay()
method that the index handler calls:
def getReportForDay(self,inDate=None, submit=None, filePrint=None):
The first parameter to all methods is always self, and represents your request handler class. Any parameters defined after that will be taken either from the URL (via GET processing) or from the body via a POST. So, from looking at this method, we can see that the handler is looking for 4 parameters. You could call this method like this:
http://localhost:port/examplePlugin/examplePlugin/getReportForDay?inDate=SOMEDATE&submit=OK&filePrint=no
It would also be processed via a POST with those values in the form. Now that you know how to construct request handlers, let’s talk about how the reqhandler.py
file can communicate with IndigoServer. : add the appropriate stuff here.
So, now you know how to receive a request and how to communicate with IndigoServer. The last piece is how to display the page in the user’s browser. The simplest answer is this: return an HTML string. However, generating HTML manually can be pretty ugly depending on how complex the HTML is. We addressed this problem by integrating the Cheetah template engine into IWS.
In a nutshell, a Cheetah template file is an HTML file with markups in it. Cheetah will allow many Python variable types to be embedded in your HTML template. It also offers some basic flow-control statements like IF and FOR. Check the Cheetah documentation for the complete set of options.
For instance, here is a small snippet of a Cheetah template that generates table rows, with alternating color, one row for each string in an array:
<table> <tr> <td colspan=2 align="center"><h1>Report for $inDate</h1></td> </tr> <tr><th align="left">Device Name</th><th>Minutes ON</th></tr> #set $i=1 #for $deviceKey in $totalKeys #if (($i % 2) != 0) <tr class="darkRowBg"> #else <tr class="lightRowBg"> #end if #set $i=$i+1 <td class="deviceName">$deviceKey</td> <td class="deviceStateOff">$totals[$deviceKey]</td> </tr> #end for </table>
First we start the table and put in a header row. Then the local variable, $i
, is declared and set to 1. Then we loop for every key in the $totalKeys
associative array. If $i
is an odd number, we make the background for the row dark, otherwise we make it light. We then put the key (which in this case is a device name) in column 1 of the row, and something else (in this case the # of minutes the device was on) in the second column.
Cheetah templates can contain javascript, CSS, etc. - basically everything a normal HTML page can. lines that start with #
are interpreted by the template engine, and words starting with $
are interpreted as variables.
Now that you have your template defined, how do you get it processed? That’s actually pretty simple. In your request handler method, you do four things: 1) get (and lock) the plugin template, 2) set the variables used in the template, 3) tell the engine to render the template into a full HTML document, 4) unlock the template.
Here’s an example of how you might render the template file that the snippit above came from:
tmpl = self._GetAndLockPluginTemplate('usageReporter/templates/usageReport.html') try: tmpl.totals = totals tmpl.totalKeys = totalKeys return tmpl.RenderTemplate() finally: tmpl.ReleaseLock();
So, the first line gets the template and locks it so that it can’t be altered during the rendering. The next step is to process the template. Notice that we put all the template processing code inside a try block - we have to do that because we must always release the lock on the template no matter what errors might be encountered during the processing of the template.
Inside the try block, we see these lines:
tmpl.totals = totals tmpl.totalKeys = totalKeys
These two lines set the cheetah template variables (tmpl.totals
and tmpl.totalKeys
) to some the local Python variables (totals
and totalKeys
). This is how you pass data to a template - you just assign the template variable a value from your Python code.
Next, we want to process the template:
return tmpl.RenderTemplate()
This line calls the Cheetah RenderTemplate()
method that actually processes your template file along with the variables you pass in and creates a fully rendered HTML template. That gets returned by your handler to the browser for display.
The last two lines are critical:
finally: tmpl.ReleaseLock();
This will ensure that the template is unlocked even if an error occurred during template processing. Not doing so will cause this template to be unavailable until IWS is restarted.
So, that’s how an IWS plugin is built. It’s quite flexible and can be used for a variety of purposes, from getting data into Indigo (have a handler that “catches” a POST or GET from some other system and stored data into Indigo variables) to creating custom web-based UIs.