The plugin name must have a maxium size of 8 characters and must be lower case. This restriction is linked to xPL.
The plugin version must respect the rules for version numbers.
At least, a plugin consist of the following files :
Sometimes a plugin could also need some data files.
First, you should develop the library. When the library is functionnal or when some features of your library are functionnal, you could start the bin part. Then, create the json file and finish with the needed xml files for stats and url2xpl.
There are some rules to follow when developing a plugin.
See PEP 8
Private instance variables that cannot be accessed except from inside an object don’t exist in Python. Although there are recommended conventions they still remain more or less evasive. So we decided to adopt our own conventions following as close as possible the PEP8 ones.
Classes:
Modules (no class)
The plugin configuration is described in the json file. Example:
"configuration": [
{
"default": "False",
"description": "Automatically start plugin at Domogik startup",
"id": "0",
"interface": "no",
"key": "startup-plugin",
"optionnal": "no",
"options": [],
"type": "boolean"
},
{
"default": "/dev/teleinfo",
"description": "Teleinfo device (ex : /dev/ttyUSB0 for an usb model)",
"id": "1",
"interface": "no",
"key": "device",
"optionnal": "no",
"options": [],
"type": "string"
},
{
"default": "60",
"description": "Interval between each request (seconds)",
"id": "2",
"interface": "no",
"key": "interval",
"optionnal": "no",
"options": [],
"type": "number"
}
],
The first configuration key must always be startup-plugin. For each plugin, this key is checked on Domogik startup. If it is set to True, the plugin will be started on Domogik startup.
The other configuration keys will be read by your plugin on plugin startup. Example:
def __init__(self):
# initialize the plugin
XplPlugin.__init__(self, name='teleinfo')
# get the config object
self._config = Query(self.myxpl, self.log)
# read the configuration elements
device = self._config.query('teleinfo', 'device')
interval = self._config.query('teleinfo', 'interval')
...
Some plugins will need to handle several interfaces. For each of these interfaces you may need to configure several parameters. This is the case of the yweather plugin were you may want to check the yeather from several places in the world.
For each interface, one parameter can be used as a device address. Example for yweather : you set the numeric city code as city and you set the city name as device. When creating a device, you will just have to fill the device address with the city name.
Json example:
"configuration": [
...
{
"default": null,
"description": "City code. See http://weather.yahoo.com to find code",
"id": "4",
"interface": "yes",
"key": "city",
"optionnal": "no",
"options": [],
"type": "string"
},
{
"default": null,
"description": "Device address for this city (ex : weather_home)",
"id": "5",
"interface": "yes",
"key": "device",
"optionnal": "no",
"options": [],
"type": "string"
}
To read the configuration of each interface you had to loop on the query config and use an index. Example:
def __init__(self):
# initialize the plugin
XplPlugin.__init__(self, name='yweather')
.... get config elements not linked to the interfaces ....
# create an object to store the interfaces
self.cities = {}
# set the index and start looping
num = 1
loop = True
while loop == True:
# try to get the values for each key of the interface
city_code = self._config.query('yweather', 'city-%s' % str(num))
device = self._config.query('yweather', 'device-%s' % str(num))
# if one mandatory value is not *None*, store the configuration element.
# else, end the loop
if city_code != None:
self.cities[city_code] = { "device" : device }
num = num + 1
else:
loop = False
...
A plugin can change its configuration values. You should avoid to do this if you can as the configurations values should be set by the user!
Example: for a plugin foo and a configuration key named bar:
self._config.set('foo', 'bar', 'newvalue')
In a plugin there are dedicated function to log data in the log files.
There are several log levels allowed :
To log, just use one of the following functions:
self.log.error("this is an error message")
self.log.warning("this is a warning message")
self.log.info("this is an info message")
self.log.debug("this is a debug message")
The self.log object is created when you call XplPlugin.__init__(...)
There is no need to write a dedicated log line to indicate that plugin is starting. The call to XplPlugin.__init__ will do this like this:
2010-06-18 10:48:08,585 domogik-myplugin INFO ----------------------------------
2010-06-18 10:48:08,585 domogik-myplugin INFO Starting plugin 'myplugin' (new manager instance)
When you want to use more than one log file, you can declare a new logger like this:
log_new = logger.Logger('myplugin-newlog')
self._log_new = log_new.get_logger()
self._log_new.info("This is an info")
There are a few things to avoid to make sure your plugin will stop correctly : * No while True * No sleep(xx)
The first one will never end, the second one will hang during xx seconds even if we asked the plugin to stop.
All the plugins have a method get_stop() provided by the XplPlugin class that they extends from. This method returns a threading.Event instance which is set when the plugin is asked to stop.
Replace:
while True:
do_something()
With:
while not self.get_stop().isSet():
do_something()
Replace:
sleep(60)
With:
self.get_stop().wait(60)
which will exit as soon as the Event is set, whereas sleep() will wait the whole time.
If you need to use such a feature in the library part of your plugin, you can do something like :
In bin/myplugin.py:
from domogik.xpl.common import XplPlugin
from domogik.xpl.lib import myplugin
class MyPlugin(XplPlugin):
def __init__(self):
self._mypluginlib = myplugin.MyPluginLib(some, other, param, self.get_stop())
In lib/myplugin.py:
class MyPluginLib:
def __init__(self, some, other, param, stop):
self._some = some
self._other = other
self._param = param
self._stop = stop
def some_method(self):
while not self._stop.isSet():
do_something()
self._stop.wait(60)
If you need to do some more things when your plugins shutdowns (close devices, files, ...), you can use the add_stop_cb method provided by the XplPlugin class. For example: bin/myplugin.py:
from domogik.xpl.common import XplPlugin
from domogik.xpl.lib import myplugin
class MyPlugin(XplPlugin):
def __init__(self):
self._mypluginlib = myplugin.MyPluginLib(some, other, param)
self.add_stop_cb(self._mypluginlib.stop)
lib/myplugin.py:
class MyPluginLib:
def __init__(self, some, other, param):
self._some = some
self._other = other
self._param = param
def stop(self):
#do something here, like close your serial port or network socket or ...
Calling a function/method of the bin part from the library is very usefull : typically an event is fired and catched by the lib module of your plugin. Typically an event is fired and catched by the lib module of your plugin. Then you’d like to send a xPL message. How can you do that?
You have to use a callback function, here is how to do it:
In your bin module, you define a function that sends a xPL message:
def send_xpl(self, param1, param2):
msg = XplMessage(...)
...
In your bin module, somewhere you instantiate your lib class, and pass the callback method:
myLib = MyLib(p1, p2, cb = self.send_xpl)
So in your lib module you will have something like:
class MyLib:
def __init__(self, p1, p2, cb):
self.callback = cb
....
def my_method_that_will_send_a_xpl_message():
...
self.callback(param1, param2)
To create a timer, first you must import:
from domogik.xpl.common.xplconnector import XplTimer
import threading
Then, in the code do this:
timer = XplLTimer(60, my_callback_function, self.myxpl)
timer.start()
60 is the interval between each call of my_callback_function. The function will be called first on timer startup, and then each 60 seconds.
When you want to stop the timer, just do:
timer.stop()