Gateway plugins

From OpenMotics
Revision as of 19:54, 7 March 2014 by Fryckbos (talk | contribs)
Jump to navigation Jump to search

The plugin system on the OpenMotics Gateway allows users to run python code on the gateway. This code can interact with the OpenMotics Master through the webservice, expose new methods on the webservice, receive events for input and output changes and run background tasks.

Writing a plugin

The plugins are written in Python 2.7 and creating a new one is as easy as extending the OMPluginBase. Three class variables should be defined: name (String), version (String with format x.y.z with x,y,z are integers) and interfaces (a list of tuples (interface name, version)). You will find more information about the interface system below.

class MyPlugin(OMPluginBase):
    name = 'MyPlugin'
    version = '0.0.1'
    interfaces = [ ('webui', '1.0') ]

The @om_expose decorator

Decorator to expose a method of the plugin class through the webinterface. The url will be /plugins/<plugin-name>/<method>.

Normally an authentication token is required to access the method. The token will be checked and removed automatically when using the following construction:

def method_to_expose(self, ...):

It is possible to expose a method without authentication: no token will be required to access the method, this is done as follows:

def method_to_expose(self, ...):

The @input_status decorator

Decorator to indicate that the method should receive input status messages. The receiving method should accept one parameter, a tuple of (input, output). Each time an input is pressed, the method will be called.

Important ! This method should not block, as this will result in an unresponsive system. Please use a separate thread to perform complex actions on input status messages.

def recv_input_status(self, status):
    (input, output) = status

The @output_status decorator

Decorator to indicate that the method should receive output status messages. The receiving method should accept one parameter, a list of tuples (output, dimmer value). Each time an output status is changed, the method will be called.

Important ! This method should not block, as this will result in an unresponsive system. Please use a separate thread to perform complex actions on output status messages.

def recv_output_status(self, status):
    for s in status:
        (output, dimmer_level) = s

The @background_task decorator

Decorator to indicate that the method is a background task. A thread running this background task will be started on startup.

def run(self):

The @on_remove decorator

Decorator to indicate that the method should be called just before removing the plugin. This can be used to cleanup files written by the plugin. Note: the plugin package and plugin configuration data will be removed automatically and should not be touched by this method.

def uninstall(self):


The purpose of the interface system is that generic interface can be defined, those interfaces can be implemented by the plugins. For example an audio interface could define methods for setting the channel, the volume and so on. An concrete implementation of the audio interface can then be created for each type of audio device. This allows for a generic web page for controlling audio devices with different backends for different audio devices.

An interface defines a set of methods on the plugin that are exposed on the webinterface. At the moment only two interfaces are defined:

The webui interface

The webui interface allows the user to expose a html page on the gateway web interface. The gateway web interface shows an extra tab for each plugin that implements the webui interface, the tab contains an iframe with the content of the 'html_index' method.

The webui defines 1 method: 'html_index' that requires authentication and takes no arguments. For example:

def html_index(self):
    return "<html><body>Hello world</body></html>"

The config interface

The config interface provides a generic way to configure a plugin. It exposes a method that returns the configuration description (this contains the fields and types in the configuration), a method for setting and a method for getting the configuration. These methods should return json serialized objects. See the section 'Plugin configuration' below for more information about the configuration and configuration description objects.

The config interfaces contains:

* get_config_description: returns a list of objects (each object defines one field in the configuration).
* get_config: returns the configuration object
* set_config(config): config is the configuration object, returns { 'success' : true }

Plugin configuration

Putting it all together

Below is the code for a logging plugin that keeps track of the last 100 events (here an event is an input or output change):

from plugins.base import om_expose, input_status, output_status, OMPluginBase, PluginConfigChecker
from datetime import datetime
import simplejson as json

class Logger(OMPluginBase):
    """ This example plugin shows in memory logging of the last input and output status events.
    The plugin can be configured to disable or enable the logging of input and output events.
    name = 'Logger'
    version = '1.0.0'
    interfaces = [ ('webui', '1.0'), ('config', '1.0') ]

    config_descr = [
        { 'name' : 'log_inputs',  'type' : 'bool', 'description': 'Log the input data.'  },
        { 'name' : 'log_outputs', 'type' : 'bool', 'description': 'Log the output data.' }

    default_config = { 'log_inputs' : True, 'log_outputs' : True }

    def __init__(self, webinterface, logger):
        super(Logger, self).__init__(webinterface, logger)
        self.__config = self.read_config(Logger.default_config)
        self.__config_checker = PluginConfigChecker(Logger.config_descr)
        self.__logs = []

        self.logger("Started Logger plugin")

    def __check_length(self):
        if len(self.__logs) >= 100:
            self.logger("Chopping the logs")
            self.__logs = self.__logs[-100:]

    def recv_input_status(self, status):
        if self.__config['log_inputs']:
            self.__logs.append("%s -- Input pressed: %s -- Output changed: %s" % (, status[0], status[1]))

    def recv_output_status(self, status):
        if self.__config['log_outputs']:
            self.__logs.append("%s -- Outputs on: %s" % (, status))

    def html_index(self):
        return "<br/>".join(self.__logs)

    def get_config_description(self):
        return json.dumps(Logger.config_descr)

    def get_config(self):
        return json.dumps(self.__config)

    def set_config(self, config):
        config = json.loads(config)
        self.__config = config
        return json.dumps({ 'success' : True })

Packaging a plugin

A plugin is packaged as a tgz file containing the content of the plugin package. The plugin class should be defined in and an file should be provided. Extra python files can be included in the package. The following commands can be used to create a package and to calculate the md5 sum (required for the plugin installation):

tar czf plugin.tgz
md5sum plugin.tgz

Installing a plugin