Yesterday’s post introduced HouseAgent. In today’s post we will dive deeper into HouseAgent plug-ins and their architecture. HouseAgent plug-ins can be programmed in any language, but Python is by far the preferred language for plug-ins.
The previous post mentioned that a plug-in is a completely isolated component in the HouseAgent network. While this is true for the plug-in itself, it doesn’t apply to the plug-ins GUI pages. These GUI pages hook into the coordinator directly.
Inside the plug-in you have total freedom of what you want to do with it, do you want to talk to a serial port? No problem. Do you want to fetch a website and report those values? No problem either. Maybe you want to read out a certain file generated by another program? Guess what..
Although you have total freedom of what happens inside the plug-in, there are certain rules to stick to when it comes to reporting to the broker. This is required for other components to understand the messages send by your plug-in. To provide the plug-in developer with an easy way to interact with the broker and use certain standardized messages (value updates, power on commands etc.) HouseAgent provides a plug-in API.
Plug-in API
Let’s look at some basic usage of the plug-in API. Creating a basic plug-in connection to the broker is an easy, 2 line process.
from plugins.pluginapi import PluginAPI pluginapi = PluginAPI('922d6504-b58f-4d9a-b6eb-870aa06f2d00', 'TestPlugin')
The first argument is a unique GUID for the plug-in, a GUID can be created in the coordinator web interface:
The second argument is a plug-in type, this type can be used to identify plug-ins of the same kind. Optionally you can supply the following arguments:
- broker_ip (default: ‘127.0.0.1’)
- broker_port (default: 5672)
- username (default: ‘guest’)
- password (default: ‘guest’)
- vhost (default: ‘/’)
These arguments are useful when you want to connect to a RabbitMQ broker other then on the localhost (yes, you could connect to a broker based in Sydney from Amsterdam!), or you could use different credentials for example. Of course you are not limited to setting up one plug-in connection, you could connect to multiple brokers for use in high availability scenario’s.
After you are connected to the broker you can use some built-in API commands to send data to the broker. Here is an value update example taken from one of the plug-ins I developed:
values = {'Light': str(light), 'Humidity': str(humidity), 'Temperature': str(temperature), 'Motion': str(motion), 'Battery': str(battery)} pluginapi.value_update(node_id, values)
As you can see, you can supply an dictionary Python object with an unlimited number of values. What you do need to supply is the node address, this is an unique value to identify a device. A device is identified in the database based up-on a plug-in ID (which is the GUID) and a device address (which could be node number 1, 2, 3). The plug-in API also allows you to register to certain events, one example would be the power_on command (i.e. turn on a lamp module). Here is a basic registration, and callback function for the power_on command:
# Register ourselves to receive power-on callbacks pluginapi.register_poweron(self) # This is the callback function def on_poweron(self, address): self._set_node_on(int(address)) return {'processed': True}
All the helper functions supplied by the plug-in actually translate to AMQP messages, which can get quite complex. For the ease of development this is hidden from the developer. There is still an option though to talk directly to the broker (for example if you prefer a different language then Python). One other important thing to note is that error handling is done by the plug-in API, so you don’t need to take care about for example connection failures.
Plug-in GUI – Menu’s
HouseAgent allows you to create your own custom GUI for your plug-in, but you may still use standardized functions for your plug-in (for example for adding a device). You can also re-use the HouseAgent master template, for styling. One of the handy features is the menu.xml file, you can use this file to create your own custom menu items but still use the standard HouseAgent menu style which looks like this:
To create a custom menu you need to create a menu.xml file in the templates/plugins/yourpluginname/ folder. Here is an example of what the menu.xml file should look like:
<items> <item name="Latitude" url="#latitude" image="/images/plugins/latitude/latitude_icon.png"> <subitem> <name>Latitude approval</name> <url>/latitude_approval</url> <description>Setup Google Latitude approval, to allow HouseAgent access to your location data.</description> </subitem> <subitem> <name>Manage accounts</name> <url>/latitude_accounts</url> <description>Manage Google Latitude accounts and approval.</description> </subitem> <subitem> <name>Manage known locations</name> <url>/latitude_locations</url> <description>Manage latitude known locations. You can for example set your home location in this page.</description> </subitem> </item> </items>
The structure of this file speaks for itself.
Plug-in GUI – Pages
Now that we can create our own menu entry’s and get them visible in HouseAgent it’s time to actually create those pages. In order to host custom pages for your plug-in you need to create a file in the pages/ folder. You can just create a yourpluginname.py file inside that folder. There is one requirement, and that is to create an entry point function inside your pages file. The entry point function (exact name must be init_pages!) looks like this:
def init_pages(web, coordinator, db): web.putChild("zwave_add", Zwave_add(coordinator, db)) web.putChild("zwave_networkinfo", Zwave_networkinfo(coordinator, db)) web.putChild("zwave_added", Zwave_added(coordinator, db))
As you can see there are three important arguments passed, argument one (web) is an instance to the web controller of HouseAgent. You can extend this object to allow your own pages, this is done using web.putChild() function as listed above. The second argument is an instance of the coordinator, you can use this to interact with the broker (for example send a power_on command to the network). The last argument is an instance of the database, you can use this to interact with the HouseAgent database. Once you have defined your entry point function you can use web.putChild to extend the web object with a custom page class. Here’s an example of a custom web page (zwave_add):
class Zwave_add(Resource): """ Class that shows an add form to add a z-wave device to the HouseAgent database. """ def __init__(self, coordinator, db): Resource.__init__(self) self.coordinator = coordinator self.db = db def result(self, result): lookup = TemplateLookup(directories=['templates/']) template = Template(filename='templates/plugins/zwave/add.html', lookup=lookup) self.request.write(str(template.render(result=result[1], locations=result[0], node=self.node, pluginid=self.pluginid, pluginguid=self.pluginguid))) self.request.finish() def render_GET(self, request): self.request = request self.node = request.args["node"][0] self.pluginguid = request.args["pluginguid"][0] self.pluginid = request.args["pluginid"][0] deferlist = [] deferlist.append(self.db.query_locations()) deferlist.append(self.coordinator.send_custom(self.pluginguid, "get_nodevalues", {'node': self.node})) d = defer.gatherResults(deferlist) d.addCallback(self.result) return NOT_DONE_YET
This page is actually a bit complex, because there is some interaction with the coordinator. But it should give you an idea of what custom pages look like.
Plug-in GUI – Templates
HouseAgent also allows you to use templates to display data. For this the Mako template engine is used. As you can see in the above example page there is a template function which refers to “templates/plugins/zwave/add.html”. This is actually a Mako template file which looks like this:
<%inherit file="/master.html"/> <%def name="content()"> <div id="output"></div> <table cellspacing="0" cellpadding="0" align="center" width="100%" id="devices"> <tbody> <tr> <td class="HeadText">Add z-wave device to the HouseAgent database</td> </tr> <tr> <td class="body info"> <p>This page allows you to add this device to the database. Please select the values you would like to log..</p> </td> </tr> <tr> <td class="body"> <form name="device_add"> <table class="InfoTable" cellspacing="0" cellpadding="2" border="0" width="40%"> <tbody> <tr> <td class="Head2" colspan="3">Device properties</td> </tr> <form> <tr> <td class="Label"> <span class="requiredfield">*</span> Device name: </td> <td> <input class="Field250" name="name" id="name"> </td> </tr> <tr> <td class="Label"> <span class="requiredfield">*</span> Location: </td> <td> <select id="locations"> % for location in locations: <option value="${location[0]}">${location[1]}</option> % endfor </select> </td> </tr> </tbody> </table> </form> </td> </tr> <tr> <td class="body"> <table class="InfoTable" cellspacing="0" cellpadding="0" border="0" width="40%"> <tbody> <tr class="HeadTable"> <td nowrap="" width="5%"></td> <td nowrap="" width="30%">Command Class</td> <td nowrap="" width="30%">Label</td> <td nowrap="" width="30%">Value</td> </tr> % for value, details in result.iteritems(): <tr class="Row"> <td><input type="checkbox" id=${value}></td> <td>${details["class"]}</td> <td>${details["label"]}</td> <td>${details["value"]} ${details["units"]}</td> </tr> % endfor </tbody> </table> <br> <table class="PanelPlain" cellspacing="0" cellpadding="2" border="0" width="100%"> <tbody> <tr> <td height="30" valign="top"> <button id="adddevice_button">Add device</button><br><br> </td> </tr> </tbody> </table> </td> </tr> </tbody> </table> </%def>
In the first line we extend from the master.html and re-use that style to create our plug-in page. One other important thing used here is for loop in the template to display locations in a select box. You can use all of the Mako templates features which is beyond the topic of this blog post to describe. But, I suggest you check out the Mako template website for more information.
This concludes the post about HouseAgent plug-ins. If anything is unclear please leave a comment or use the HouseAgent forum.