Table Of Content¶
Technical and developers documentation¶
If you are looking for installing and using Domogik, please check the user documentation.
Technical Documentation¶
Todo
Please notice that this documentation is not fully up to date for Domogik 0.5.
xPL Hub¶
Message queue (zeroMQ)¶
Core component : admin and rest¶
Developers Documentation¶
Todo
All this part is currently in progress
Quick overview¶
What is Domogik in few words ?¶
Domogik is a free home automation solution based on xPL. It allows to use several User Interfaces (you can even create your own interface if you want). Domogik is modular : you can add and remove plugins easily and create your own plugins. A plugin is based on a technology (or part of a technology).
How does it work ?¶
Domogik is made of several core components. Each core component has some dedicated tasks. All components interact between them thanks to a message queue : Zero MQ. As Domogik uses xPL for automation purpose, some core components are plugged to the xPL network which is used by the xPL plugins. There is also one database to store configuration and data of all the sensors.
Components overview¶
Database¶
The database will store all configuration parameters, all device parameters and all the sensors history.
Message queue (MQ)¶
The message queue is very important for Domogik. All the domogik internal communications are sent and receive over the MQ. The plugins also use the MQ to interact with core components. Hopefully, the plugin developers don’t need to focus on the MQ : all is handled by some generic functions, so the developers can focus on their plugin features.
xPL hub¶
The xPL hub is just a hub dedicated to xPL. All xPL clients (domogik plugins for example) must first connect themself to the hub and then the hub will deliver all xPL messages to the appropriate targets. If an automation installation has several servers, each server will have its own xPL hub.
xPL plugins¶
A xPL plugin is currently (0.4 release) the only kind of package on Domogik side. One day, there will be some other kind of packages, but this is another story ;). A xPL plugin can handle some automation hardware (x10, plcbus, zwave, knx, ...) or some online services (Yahoo Weather, ...). The plugin will send or receive some xPL messages to send sensor data (light status, temperature, ...) or receive commands to apply (switch on, switch off, ...).
With Domogik, the developer won’t need to focus on the xPL protocol specification (heart beat messages, hub discovery, etc) : all is handle by some generic functions and the developer will only need to focus on sending and receiving the xPL messages related to his plugins features.
Manager¶
The manager is the core component which will monitor all the other components (core or plugins). When a component start, it will send its status to the manager and this one will look on it during all its life. The manager start and stop plugins (depending on the user actions on the user interface or during the startup sequence).
Dbmgr¶
The dbmgr (database manager) is used to manage the plugin configuration. When a plugin need some configuration parameters, it will query the dbmgr over the message queue.
Rest¶
Rest is a REST service which is used by the user interfaces to manage devices, send commands, get the sensors data, ...
XplGW¶
XplGW (xPL gateway) is the gateway between the xPL world and the database. This component store in memory the plugin xpl interfaces (thanks to the manager) and is able to translate all received xPL messages into informations that can be stored in database. This component is also used by Rest (over the MQ) to send xPL commands.
Scenario¶
The scenario component handle the scenario management.
Admin¶
This component is very important, this is the only user interface delivered in Domogik. This user interface will allow you to configure and manage your Domogik installation. This is not a control interface for your automation system! This is an interface to configure your automation system. To control your automation system, you will need to install another user interface like Domoweb or Domodroid
Domoweb and Domodroid¶
These are not components of Domogik. These are some user interfaces to control your automation system over Domogik. You can install them if you want, but you can also develop your own user interface if you prefer to do so.
Why xPL ?¶
xPL is an open protocol intended to allow the control and monitoring of home automation devices. The primary design goal of xPL is to provide a rich set of features and functionalities, whilst maintaining an elegant, uncomplicated message structure. The protocol includes complete discovery and auto-configuration capabilities which support a fully ‘plug and play’ architecture essential to ensure a good end-user experience. xPL benefits from a strongly-specified message structure, required to ensure that xPL-enabled devices from different vendors are able to communicate without the risk of incompatibilities.
—From xPL official website
xPL is really a useful protocol for automation. It is quite easy to implement of small electronic devices like arduino and it is used by other automation systems. Thanks to xPL you may plug 2 (or more) automation systems together, for example Domogik and xPL-perl.
Core development¶
Todo
Add some informations for core developers
Releasing tasks¶
0.5.x releasing tasks¶
In a 0.5-hotfix branch :
- If needed, update src/domogik/__init__.py (this should already be done!)
- Check the milestone is finished in the tracker
- Create a new release note docs/users/releases/
- Create a blog post in all languages
- Prepare some artwork for twitter and the website
- Update the website index and complete the screenshot pages if needed
- Add the new release note in docs/contents.txt and docs/index.txt and docs/users/installation/index.txt
- Update docs/users/installation/index.txt with the new packages path
- Tag
- Test the install
- Merge in master
- Merge in develop
- In 0.5-hotfix, change the number to the next release (0.5.2 => 0.5.3 for example)
- Website : update the download page
- Website : publish (blog post + download page + index + screenshots)
- Do the same steps if needed for Domoweb
Obsolete - Resources¶
- branching model to follow : http://nvie.com/posts/a-successful-git-branching-model/
Obsolete - 0.4.0 specific tasks¶
- apply the branching model and communicate on it with all developers! * master => release-0.4 * create develop from master
- write doc about it for plugins
- write doc about it for the core (add a dedicated section about core dev in the doc) : hotfixes, features branches, ...
- set up the new download manager
Obsolete - Before releasing¶
Here are the tasks to do before releasing a package:
- review the quality (pylint)
- review the security with Wapiti
- check the tickets about this release
- decide if this is alpha/beta/candidate/final
- create the release-0.4.0 branch (from master in 0.4.0, from develop after) which will be used for the final steps (this allow to continue developments for next releases in the develop branch)
- check the release doc page and fill it if necessary
Specific tasks for the final release¶
- update the install documentation to use the package instead of git
- doc: create the release doc : update banner, create a dedicated cron task which use the release-0.4.0 branch (for example)
- update the roadmap for the next releases and communicate about it (at least a tweet + mail to ML to the updated page)
Obsolete - Packaging¶
- update package.sh
- generate the package, check its content
- test the package (installation, basic tests)
- set the tag on git master branch
- upload the package
- add it on wordpress
- create the delivery articles
- create the mail for ML
- tweet the release : link to the website article
Test security of the Administration interface with Wapiti¶
Purpose¶
Before releasing, we must check the security of the administration web interface thanks to Wapiti : http://wapiti.sourceforge.net/
Install¶
$ wget http://sourceforge.net/projects/wapiti/files/wapiti/wapiti-2.3.0/wapiti-2.3.0.zip
$ unzip wapiti-2.3.0.zip
$ cd wapiti-2.3.0/bin/
$ chmod u+x *
$ sudo python setup.py install
Do the test!¶
Test 1 : no authentication¶
First, run Domogik as usual. Then, simply run wapiti without options:
$ ./bin/wapiti http://192.168.1.10:40406
Here is an example of what you should get:
Todo
Update in english
Wapiti-2.2.1 (wapiti.sourceforge.net)
..
Note
========
Cette session de scan a été enregistrée dans le fichier /home/fritz/scans/192.168.1.10:40406.xml
Vous pouvez l'utiliser pour lancer des attaques sans scanner à nouveau le siteweb avec le paramêtre "-k"
[*] Chargement des modules :
mod_crlf, mod_exec, mod_file, mod_sql, mod_xss, mod_backup, mod_htaccess, mod_blindsql, mod_permanentxss, mod_nikto
[+] Lancement du module crlf
[+] Lancement du module exec
[+] Lancement du module file
[+] Lancement du module sql
[+] Lancement du module xss
[+] Lancement du module blindsql
[+] Lancement du module permanentxss
Rapport
------
Un rapport a été généré dans le fichier generated_report
Ouvrez generated_report/index.html avec un navigateur pour voir ce rapport.
Here there are no errors.
Test 2 : authentication is done¶
To authenticate with wapiti, you must download wapiti sources, for example:
$ wget http://sourceforge.net/projects/wapiti/files/wapiti/wapiti-2.3.0/wapiti-2.3.0.zip
$ unzip wapiti-2.3.0.zip
$ cd wapiti-2.3.0/bin/
$ chmod u+x *
Then, assuming http://192.168.1.10:40406/login is the login page, just do:
$ ./wapiti-getcookie /tmp/cookies.txt http://192.168.1.10:40406/login
<Cookie session=eyJfaWQiOnsiIGIiOiJOakV6TUdFd09EUTBaV05tT0dWaFl6Z3hOR1ZtWlRFME56UmlOMlppTkdNPSJ9fQ.BchpcA.XHRIKY8BaXEcC_r8FJfiRujtiZk for 192.168.1.10/>
Please enter values for the following form:
url = http://192.168.1.10:40406/login
user (default) : admin
passwd (letmein) : 123
<Cookie session=eyJfZnJlc2giOnRydWUsIl9pZCI6eyIgYiI6Ik5qRXpNR0V3T0RRMFpXTm1PR1ZoWXpneE5HVm1aVEUwTnpSaU4yWmlOR009In0sInVzZXJfaWQiOjF9.BchpeA.-vOHvUD-cDyDahfuZ5AcLuI2Udw for 192.168.1.10/>
You can now launch wapiti:
$ wapiti http://192.168.1.10:40406 --cookie /tmp/cookies.txt -v 2 -x http://192.168.1.10:40406/logout
Todo
We still need to check the all urls are called and the auth has been correctly done
Commands¶
Warning
All the actuators are not listed in this page. This page purpose is to give some examples.
A command is an action that can be done on a device, like switching it on or off, setting the dimmer value, ...
The opposit of a command is a Sensor.
Note that some devices can have both commands and sensors. Typically this is the case of a thermostat which can be controlled and can send back a value (the temperature).
A command is defined in the the plugin json file. On device creation the commands defined in the plugin json file and linked to the to device type of the created device will be inserted in the database for this device. A command can be executed by calling a rest url /cmd.
A command can have parameters that are required to successfully execute the command, for example the dimmer level for a light.
In the 0.4 release a command has a one on one relationship with an xplcommand, an xplcommand is a part of the plugin json file and define the way a xPL message which will be sent to the plugin.
A command has a list of required parameters that are needed to handle it, for example to set the level on a dimmer the command needs a level to set the dimmer, this level parameter is a required parameter to be able to complete the command.
Network ports¶
Sensors¶
A sensor is used to store data received from the xpl network inside the database, this data can then be used to generate charts and display the evolution of some data.
The data stored in the database is linked to a certain Domogik datatype <package_development/plugins/data_types/index>. This datatype is used to interpret the data and to display the correct unit.
- A sensor can have different types:
- absolute: store the value as its received
- incremental: store the difference between the last received and the last-1 received value
- calculated: a calculated value, see the specific section below
- Some examples of sensors:
- Temperature sensor
- Energy sensor (KwH)
- Power sensor (Watt)
- boolean input sensor
- ...
Calculated sensors¶
A calculated sensor is a sensor where the stored value is calculated. as an example lets use a power sensor that transmits the number of KWh used, and we want to calculate the cost for the electricity of that interval.
If we then create a calculated sensor with the below formula we can store the price for the power consumption during that time.
def calculate(value):
result = value * 0,11
return result
- The above function wil return the value to store, note this function can contain any python code you want, but there are some rules to follow:
- the function name is always ‘calculate’
- it has one input parameter named ‘value’
- it has one return parameter, a float
Tools¶
Overview¶
Some tools are installed with Domogik, they all starts by dmg_. The others are available only in Domogik sources. In this page, the first ones will be describes.
See the sources tools for the other tools.
dmg_dump¶
dmg_dump is a tool to listen xPL messages on the server.
Example:
$ dmg_dump
HUB discovery > starting
HUB discovery > looking for the hub. I hope there is one hub, Domogik won't work without the hub!
HUB discovery > Received HBEAT echo, HUB detected
HUB discovery > hub found, configuration in progress
2013-01-26 15:15:16.384598 - xpl-trig
{
hop=1
source=domogik-xpl_time.ambre
target=*
}
datetime.basic
{
datetime=20130126151847
date=20130126
time=151847
format1=201301261518475
}
^CKeyBoardInterrupt
It can be used with several options. See the -h option to get the full list:
$ dmg_dump -h
Usage: dmg_dump [options]
Options:
-h, --help show this help message and exit
-c Diaply data in a compress way
-t XPLTYPE Filter messages on XPL message type
-s XPLSOURCE Filter messages on XPL source field
-S XPLSCHEMA Filter messages on XPL schema field
-i XPLINSTANCE Filter messages on XPL instance
-V, --version Display Domogik version.
-f Run the plugin in foreground, default to background.
dmg_send¶
dmg_send allows you to send xPL messages from the command line.
Example:
$ ./send.py xpl-cmnd x10.basic "device=a1,command=on"
dmg_version¶
This tool will display the current Domogik version:
$ dmg_version
REST_API_release : 0.6
Domogik_release : 0.3.0
Sources_release : default.5689 (0.2.0-alpha1) - 2013-01-26 14:56 +0100
Tools¶
pylint-check.sh¶
This is a script which check all .py files from a directory to get a note about their quality.
Example (we assert $DOMOGIK_HOME is the root of your Domogik repository):
$ cd $DOMOGIK_HOME/domogik/src/domogik/bin # for example
$ $DOMOGIK_HOME/domogik/src/tools/pylint-check.sh
[...]
Good files :
./cidmodem.py;9,49;OK
./datetimemgr.py;8,71;OK
./gagenda.py;9,80;OK
./__init__.py;10,00;OK
./ipx800.py;9,38;OK
./manager.py;8,03;OK
./mirror.py;9,17;OK
./onewire.py;8,31;OK
./plcbus.py;8,33;OK
./send.py;9,57;OK
./teleinfo.py;9,74;OK
./wol_ping.py;9,32;OK
./x10_heyu.py;8,18;OK
./xbmc_not.py;9,51;OK
Bad files :
./dawndusk.py;3,17;KO
./dbmgr.py;7,67;KO
./dump_xpl.py;7,50;KO
./module_sample.py;7,14;KO
./rest.py;7,18;KO
./test.py;-10,00;KO
./test_send.py;7,83;KO
By default, a good file has a note greater or equals to 8,00/10. You can change this value in the script.
Developing a user interface¶
What should a user interface for Domogik do ?¶
Well, basically, what you want! Just keep in mind that Domogik is released with an administration user interface. This interface is used to manage Domogik, the plugins and the devices. You don’t need to handle these features and, I think you shouldn’t.
User interfaces should focus on control and visualisation:
- display the devices and their features.
- allow to control these devices features.
- allow to view the history of these devices features.
- organize devices by category, rooms or whatever you want.
Steps to follow¶
Specifications¶
First, you should do some specifications to see what you want to do and how to do it!
Note
There are already user interfaces for :
- browsers : Domoweb, phpMyDomo
- Android devices : Domodroid
- iOS devices : iDomotic
You are obviously free to create a new user interface for these platforms, but maybe you could also contribute to them ;)
If you plan to create a new iOS, Android or Windows Phone application, you may interested to try some crossplatforms solutions like Kivy.org or any other. This solutions will help you to create applications that will work on many devices (iOS, Android, ...).
Don’t forget to think about:
- the screen layouts (responsive designs for HTML user interfaces, size screens and orientation for mobile devices)
- the configuration
- performances (the user experience is really better with very fast user interfaces)
- be modern!
- graphical resources (icons, background, fonts)
- the documentation! It is maybe the main thing to do correctly : if the installation documentation is not clear, the user will not continue the installation and will try another user interface.
Read the documentation¶
You should read all the Domogik technical documentation, especially the REST and Message Queue parts. Don’t forget to look at data types </developers/package_development/plugins/data_types/index>
Prepare your project¶
You should use some online repository like github, this will allow you to easily manage your project (thanks to the bugtracker and wiki included). This will also allow users to easily contribute to your project.
You should also come and discuss with the Domogik team on irc. We can explain you some points, improve the doc if needed and give you some tips or warnings.
Start to code!¶
Here is how a user interface could be done...
First, the user is requested for the REST url (to access Domogik). There, the / url of the REST server will give you all other resources (Message Queue). Just ask this the first time and them allow the user to easily update this setting.
Then, the user could be requested to define a way to organize the devices (it can be rooms, pages, categories : you are the designer, it is up to you!). When this is done, let the user affect the devices to the rooms/pages/categories/...
To get the devices list and details, just use the /device url. You can also grab informations about only 1 device with the same url. Once you get these informations, you will be able to create some widgets for the device features.
Note
Another point of view is to create some widgets (for example a widget per data type or some complex widgets with several features of various data types), to let the user select and place them and to finish by requesting the user to select the devices features (filtered by data type) to use in the widget.
xPL hub overview¶
Domogik is now delivered with 2 xPL hubs:
- the C xPL hub which was used wince the first Domogik release.
- the new python xPL hub.
As the C hub has an issue on some servers, the python hub is now used as the default one. This new hub is better for analysing the xPL network as it maintains a list of all the xpl clients and some statistics on them (number of valid and invalid messages, last time seen, ...). It also logs all the invalid xpl messages.
F.A.Q¶
- Q: Is this xPL hub compliant with other xPL systems (like xpl-perl for example) ?
- R: Yes, of course. There was a bug in the hub included with Domogik 0.2/0.3 which blocked xpl-perl clients, but since Domogik 0.4, xpl-perl and Domogik can be used together with the Domogik xPL hub.
- Q: I want to use the C hub instead of the python hub. How can I do ?
- R: After the installation, you can switch by editing /etc/default/domogik and setting DOMOGIK_XPL_HUB to c (or python for the new hub)
- Q: Where are the hub log files ?
- R: The C hub has no log files. The python hub log files are in /var/log/xplhub/. You can also customize the loglevel in the hub config file : /etc/domogik/xplhub.cfg.
The python hub¶
Configuration file¶
The configuration file is /etc/domogik/xplhub. All the configuration items are related to the logs. The content of this file is enough to understand how to tune it. The default configuration is enough for all end users. Only the developers may need to increase the logs verbosity.
Log files¶
The default folder for the logs is /var/log/xplhub/. Several log files are created:
client_list.txt:
| Client id | Client source | Interval | Last seen | Status | Nb OK | Nb KO | |-----------------------+------------------------------------+----------+----------------------------+---------+--------+--------| | 192.168.1.10_43879 | domogik-manager.darkstar | 5 | 2013-01-06T17:14:55.641510 | alive | 11 | 0 | | 192.168.1.10_45993 | domogik-dbmgr.darkstar | 5 | 2013-01-06T17:14:59.044806 | alive | 9 | 0 | | 192.168.1.10_57712 | domogik-rest.darkstar | 5 | 2013-01-06T17:14:59.343223 | alive | 1 | 0 | |-----------------------+------------------------------------+----------+----------------------------+---------+--------+--------|
This log file contains the list of all the clients that the hub has seen, even if they have been stopped or if they disappear.
bandwidth.csv:
192.168.1.10_57712 ; 1357488915.05 ; domogik-rest.darkstar ; hbeat.app ; xpl-stat 192.168.1.50_54361 ; 1357488915.05 ; domogik-teleinfo.ambre ; hbeat.app ; xpl-stat 192.168.1.50_54937 ; 1357488915.05 ; domogik-wol_ping.ambre ; hbeat.app ; xpl-stat 192.168.1.50_56589 ; 1357488915.06 ; domogik-diskfree.ambre ; hbeat.app ; xpl-stat 192.168.1.50_33303 ; 1357488915.06 ; domogik-rest.ambre ; hbeat.app ; xpl-stat 192.168.1.50_58976 ; 1357488915.06 ; domogik-xpl_time.ambre ; hbeat.app ; xpl-stat 192.168.1.50_47922 ; 1357488915.06 ; domogik-manager.ambre ; hbeat.app ; xpl-stat 192.168.1.50_49465 ; 1357488915.06 ; domogik-dbmgr.ambre ; hbeat.app ; xpl-stat 192.168.1.50_58976 ; 1357488927.23 ; domogik-xpl_time.ambre ; datetime.basic ; xpl-trig 192.168.1.50_47922 ; 1357488927.64 ; domogik-manager.ambre ; hbeat.request ; xpl-cmnd 192.168.1.50_54937 ; 1357488927.64 ; domogik-wol_ping.ambre ; hbeat.app ; xpl-stat 192.168.1.50_56589 ; 1357488927.65 ; domogik-diskfree.ambre ; hbeat.app ; xpl-stat 192.168.1.10_45993 ; 1357488927.65 ; domogik-dbmgr.darkstar ; hbeat.app ; xpl-stat
This log file will track all the xpl messages on the network. The columns are:
- the client id (ip and port used by the xpl client)
- the timestamp
- the source
- the message schema
- the message type
invalid_data.csv: this file contains all the invalid xpl messages =.
xplhub.log: this is the hub log file.
Brain packages development¶
Note
If you think something is not clear or is missing in this documentation, please send an email to our developers mailing list : domogik-developers@lists.labs.libre-entreprise.org and explain us clearly what is not clear or what could be added.
General informations¶
Some parts of this documentation will refer to the plugins development documentation as there are a lot of things in common :
- the workflow
- the way to create a repository, to write the doc, to submit the package
Differences between a plugin and a brain package¶
A brain package is not something that is executed directly : the butler component will load each brain package it will find on the current host.
A brain package does not need to create any device, so all the related informations in the json file will be empty.
A brain can contain several translations : you can find all the available languages in the rs/ folder. Example :
./rs
./rs/nl_BE
./rs/nl_BE/DT_Temp.rive
./rs/nl_BE/...
./rs/nl_BE/DT_Humidity.rive
./rs/fr_FR
./rs/fr_FR/DT_Temp.rive
./rs/fr_FR/...
./rs/fr_FR/DT_Humidity.rive
./rs/en_US
./rs/en_US/DT_Temp.rive
./rs/en_US/...
./rs/en_US/DT_Humidity.rive
The RiveScript code is used as a basis. You can find more informations on the official website : http://www.rivescript.com/docs/tutorial
First, create a repository and write some specifications¶
See the plugin development documentation.
Branches management¶
See the plugin development documentation.
Brain package name¶
There is no restriction in the length of the brain package name (for plugins, you have a 8 characters limitation due to xPL).
Before coding : branching to ‘develop’¶
See the plugin development documentation.
Json file¶
See the plugin development documentation.
To sum up, for a brain, you need only to fill 3 parts. You have to let the others empty :
- identity
- configuration
- json_version
Example:
{
"identity": {
"author": "Fritz",
"author_email": "fritz.smh at gmail.com",
"tags": ["butler"],
"dependencies": [],
"description": "Butler package to use Wolfram Alpha",
"domogik_min_version": "0.4.1",
"name": "wolfram",
"type": "brain",
"version": "1.0"
},
"configuration": [
{
"default": "",
"description": "To get a Wolfram Alpha API key, you need to register (freely) on https://developer.wolframalpha.com/portal/signin.html",
"key": "api_key",
"name" : "Wolfram alpha API key",
"required": true,
"type": "string"
}
],
"products": [],
"commands": {},
"xpl_commands": {},
"sensors": {},
"xpl_stats": {},
"device_types": {},
"json_version": 2
}
Rivescript part¶
Python part¶
Some brain parts may need a library file to interact with some internet services (for example). You can create a python library in the lib/ folder.
Dedicated administration page¶
See the plugin development documentation.
Icon¶
See the plugin development documentation.
Documentation¶
See the plugin development documentation.
Tests¶
Note
This part is to complete. There is currently no test framework.
Your brain is ready ? Make some users test it¶
See the plugin development documentation.
Release a version of your brain¶
See the plugin development documentation.
Rivescript rules¶
Using python objects¶
Todo
Explain when and how creating a lib, how to use it (PYTHONPATH from command line), etc
Todo
Explain why [de ] and not [de] because of space : [de] * => de main instead of demain for demain
Features¶
The butler is able to tell the user which features it can handle. To allow this, you must add a special comment and a corresponding trigger. Example with the tiem feature:
// ##feature## give the date
+ give the date
@ shortcut date
+ shortcut date
...
Notice that if your feature accept a parameter, you should describe it also. Example in english:
// ##feature## calculate parameter
+ calculate *
...
Example in french:
// ##feature## calculer parametre
+ calculer *
...
You must use only generic expressions to describe the feature and not use words like “my, your, ...”. This is needed to allow natural speaking when requesting the butler to learn some commands. You can look at the domogik-brain-core package for more details about learning.
Weight¶
In rivescript, the default weight is set to 1. But the rivescript included in Domogik is patched to use a default weight of 10.
If you plan to use weight in your scripts, don’t forget about this!
Special characters¶
All the below characters are replaced by spaces before processing : ?!.,
The “-” character is not replaced as it can be used for mathematic usages. If you have a trigger that may contain a “-” in it, please handle it with “[-]”
People mistakes¶
People make sadly a lot of mistakes, especially when writing... In the rules, you may try to take the most common mistakes in account.
Plugins developement documentation changelog¶
Note
This page will be used for the next releases of Domogik (after 0.4.0) is some updates happen in the plugin format.
0.4.1¶
- Complete doc for 0.4.1
0.4.0¶
- New plugin format for Domogik 0.4.
Plugins development¶
Note
If you think something is not clear or is missing in this documentation, please send an email to our developers mailing list : domogik-developers@lists.labs.libre-entreprise.org and explain us clearly what is not clear or what could be added.
General informations¶
First, create a repository and write some specifications¶
Branches management¶
Plugin name¶
Choose a name that describes the plugin as good as possible. Notice that a plugin can not use more than 8 characters and they should be all lower case. This restriction is linked to xPL and is not a Domogik limitation.
Prepare the plugin¶
Now, you will have to create a few files for your plugin on the master branch.
Before coding : branching to ‘develop’¶
Json file¶
The info.json file it surely the most important part of a plugin. It will describe the configuration of the plugins, the features, the xPL messages to send and to listen, ... You should first read all this chapter, then make sure that you already defined the following part in your specifications :
- configuration parameters
- devices features, splitted in sensors and commands
- xPL messages used by these features
- the data to store in database
Starting to create the json file without being clear on these elements is useless!
Note
To help you to understand the way to create a json file, you can look on already existing plugins to see real examples.
Python part¶
After creating a start script (used only by the developers), you will now create the python part of your plugin.
- start.sh file
- Python part
Dedicated administration page¶
Some plugin may need some advanced configuration capabilities or some helpers feature that can be used from the administration interface. This is not mandatory.
Udev rules¶
If needed, you can add some sample udev rules files for your plugin.
Documentation¶
The documentation is a part important of a plugin! If it is not enough clear, the user may have some difficulties to use the plugin.
Tests¶
Preparing some automated tests is something really important for a plugin. It will help you to test automatically the plugin after each commit and will help you to easily validate some contributions (pull requests).
Your plugin is ready ? Make some users test it¶
Now, your plugin may be functional, documented and the tests are automated. It is time to release a version! But before, you should make some people test your plugin!
Release a version of your plugin¶
- ..todo ::
- switch to develop
- create a branch per version for readthedoc ????
- update info.json version number
- update docs/conf.py version number
- do the dev/doc/tests
- switch to master when version is ready (see previous chapter)
What is new in Domogik 0.4+ plugins format ? And why ?¶
Some big changes¶
With Domogik 0.4, the plugin format changes a lot. But don’t be afraid, it is the last big change of the plugins format : as Domogik 0.4 is the new base for the next Domogik releases, the plugins format won’t change like it did from 0.1 to 0.2 and from 0.3 to 0.4!
The 0.4 release come with a new database model, more powerful, more efficient. This model will allow to handle multi addresses technologies, like KNX or Chacon devices with the RFXCOM (for which the address is unit+id). It will also allow a more accurate choice of data types.
There are several important changes about the plugins format:
- All the xml files url2xpl and stats are not needed anymore and all what a plugin needs to know to speak xpl is centralized in a json file, named info.json.
- In Domogik previous releases, the plugins file tree was complex : there were some files in src/domogik_packages, src/share and 2 sort of file tree depending on the installation mode. Now, whatever your installation will be done (from sources, from a packaged release, from a debian package, ...), all the plugins will be installed in the same way, in the same place. And moreover, each plugin will have its own dedicated folder!
The plugins are easier to install¶
To deploy a plugin from its author sources, you will just have to grab the sources or a zip file, extract it somewhere in your filesystem and just create a symbolic link in /var/lib/domogik/domogik_packages to the plugin directory! Nothing else to do! And there is a tool which will do that for you. :doc:More informations about packages installation in the dedicated chapter </users/packages/index>.
Plugins development workflow¶
Overview¶
All the released versions of the plugins can be available on the packages repository : http://repo-public.domogik.org The developer of a plugin just need to submit its plugin (more details in step 5).
We want to avoid alpha/beta/candidates versions for a plugin :
- it may be too heavy to handle for some developers.
- it will fill the packages repository with non stable plugin releases.
So, we want each developer to release a plugin version only when the plugin release in progress is finished (you will find a checklist of what is expected in the step 5). If some people wish to use a plugin under development, they just need to use the GitHub (or whatever else) repository (more details in the step 4).
Focus on the branches¶
For any Domogik package on git, you should follow the branching model described on this page. This branching model is inspired from the one here : http://nvie.com/posts/a-successful-git-branching-model/
Basically :
- the master branch contains the last stable release
- the develop branch contains the release which is currently developed. It you have to do any upgrade or fix, you should do them in this branch.
- when a stable release is ready (in the develop branch), you create a tag (and version) on the GitHub repository and then merge it into the master branch.
Example for a new plugin¶
On the left, there is the master branch. On the right, the develop branch.

Step 1 - prepare the information files on the master branch¶
You will find the links for these actions in the index of the plugin development documentation
This step goal is to provide at least a README.md file in the master branch to help people find the plugin documentation even if no release has been released (and so the master branch contains no plugin yet).
Step 2 - switch to the develop branch¶
Before coding anything you must switch to the develop branch : as the master branch content must be the last stable release of your plugin, you must NEVER do any development in this branch (master). This branch will be filled only with merge of the tags of the stable releases done in the develop branch.
First, if this is not already done, commit the changes on the master branch (README.md, CHANGELOG, .gitignore) and push them:
$ git status
...
.gitignore
CHANGELOG
README.md
...
$ git add .gitignore CHANGELOG README.md
$ git commit -m "Initial commit"
[master (commit racine) e86b189] Initial commit
3 files changed, 9 insertions(+)
...
$ git push
...
To git@github.com:fritz-smh/domogik-plugin-teleinfo.git
* [new branch] master -> master
At this point, the master branch will only include nearly nothing. This is the goal! The master branch will be populated with the plugin data only when a stable version will be released.
Then, create the new develop branch and push it to the remote repository (on GitHub):
$ git branch develop
$ git checkout develop
$ git push --set-upstream origin develop
...
To git@github.com:fritz-smh/domogik-plugin-teleinfo.git
* [new branch] develop -> develop
...
You can now start to develop your plugin on this branch!
Step 3 - Develop the plugin¶
You will find the links for these actions in the index of the plugin development documentation
You should stay on this step until a stable version of your plugin is not ready in the develop branch. Please don’t forget that a stable version means:
- the plugin is functional and stable
- the plugin code is clean
- the info.json file is ok
- the documentation is written and handled by ReadTheDoc
- if the plugin technology allows it, some tests have been written and are functional with Travis CI
- ...
You will find a complete checklist in a later step
Step 4 - Register a development version¶
Register the plugin the packages repository¶
A development version (which can be named as alpha/beta/candidate) will never be published on the packages repository as a release. But you can submit the direct link to the zip of the last commit of the branch develop on the package repository. This will allow:
- the plugin to be identified as an existing plugin still under development
- display the plugin build status in the packages repository dashboard
Go to the the packages repository and:
- login with your GitHub account
- Click on Submit a package
- Fill the form:
- Package url : the url to the zip of the develop branch. Example : https://github.com/fritz-smh/domogik-plugin-teleinfo/archive/develop.zip
- Candidate for the category : Development (at this point, do not use any other choice!!!!)
- Build status : the url to the picture of the Travis CI build status. Example : https://api.travis-ci.org/fritz-smh/domogik-plugin-teleinfo.svg?branch=develop
- Documentation : the url to the generated documentation. Example : http://domogik-plugin-teleinfo.readthedocs.org/en/develop/
- Click on Validate
- Click again on Validate
How to give some development releases to people who want to test ?¶
The best way to test a plugin during the development phase is to clone the repository. But you can also use the .zip file of the develop branch.
Step 5 - Releasing a stable version¶
The checklist¶
Here are all the prerequisites for a plugin to be stable:
Note
- The development of the features included in the plugin version is complete
- The plugin is stable (it has been tested a complete week without any restart)
- The documentation is finished and published on ReadTheDoc
- The tests are written, set with Travis CI and the tests results are OK
- A few people have tested and approved the plugin
- There is no license issue with the code
- All self.log.* calls use the u”foo” syntax to avoid charset issues
If one of these points is not OK, the plugin will not be validated on the package repository by the core team.
Prepare the release¶
First, you need to check you have no more upgrades in progress. You can check with the git status command.
Then, check that you have pushed all upgrades on the remote repository by doing a last push with the git push command. If some commits were not pushed, well... it means that only you have tested the last plugin upgrades and so the checklist is not fully ok ;). Get back later on the step 5 after some people have tested your last upgrades!
Check the info.json file is OK : the version set in the identity part of the json file must be set to the version number you want to release. Example for a version 1.0:
"identity": {
...
"name": "teleinfo",
"type": "plugin",
"version": "1.0"
},
Check the docs/changelog.txt file is OK : the updates since the last release are described.
If this was not ok, fix this, do a commit, check that the plugin is still starting (if not, you made a mistake in the json file) and push the commit (and not, at this point you don’t need other people to test again).
Check all the existing tags for your plugin:
$ git pull --tags
$ git tag -l
Check that there is not already a tag created for the version you want to release. If so well... you are bad somewhere!
Now, set a tag for your release. For example:
$ git tag -a "1.0" -m "release 1.0"
Push the tag on the remote repository:
$ git push --tags
And finally, merge the tag in the master branch. For example for our 1.0 release:
$ git checkout master
$ git merge 1.0
Push the merge:
$ git push
And get back to the develop branch:
$ git checkout develop
You now have finished the actions on your local repository!
Check that the Travis CI build is ok for your tag¶
Go on the Travis CI page of your plugin and check in the Branch summary tab that a build has been done for your tag and is OK (you may need to wait a few minutes if you just pushed the tag). Example for the teleinfo plugin: https://travis-ci.org/fritz-smh/domogik-plugin-teleinfo/branches
Get the build status picture url for the release of your plugin and keep it somewhere for the submission. Example url for the teleinfo plugin and its release 1.0: https://api.travis-ci.org/fritz-smh/domogik-plugin-teleinfo.svg?branch=1.0
Configure ReadTheDoc to build the new version¶
Connect to your ReadTheDoc account and go in your project homepage, for example: https://readthedocs.org/projects/domogik-plugin-teleinfo/
Go in the Admin section, then in the Versions section and:
- Set 1.0 as the default version (assuming 1.0) is the last version of your package). If you can’t select it, first check the 1.0 version to activate it and click on Submit. You will now be able to select this version.
- Uncheck the stable version
- Uncheck the master version
- Uncheck the latest version
- Check the develop version
- Check all the released versions (1.0, ...)
Now, check that the documentation has been generated and get its url. It should be something like http://domogik-plugin-teleinfo.readthedocs.org/en/1.0/
Create a release on GitHub¶
This part is mandatory : when a tag have been created, you can see the corresponding release in the Release section of a GitHub repository. Creating a release will only help you to give more informations. This is not needed but this can be nice looking on your Github repositories ;)
Finally : submit the package!¶
Now, the final step! You can submit your package as a stable release!
Go on http://repo-root.domogik.org/ and lg in with your GitHub account.
Click on Submit a package and fill the form:
- Package url : the url to the zip of the released branch. Example : https://github.com/fritz-smh/domogik-plugin-teleinfo/archive/1.0.zip
- Candidate for the category : Stable. If you think your package may need to be tested by some people before setting it as stable, you should select Testing : we will be able to move it from Testing to Stable later.
- Build status : the url to the picture of the Travis CI build status. Example : https://api.travis-ci.org/fritz-smh/domogik-plugin-teleinfo.svg?branch=1.0
- Documentation : the url to the generated documentation. Example : http://domogik-plugin-teleinfo.readthedocs.org/en/1.0/
Core component : administration user interface¶
Purpose¶
Since Domogik 0.4, the administration user interface is no more included in Domoweb.
Technical details¶
The administration user interface server is made with Flask, a python framework. Bootstrap is used for the rendering part.
Butler technical details¶
The butler component is based on RiveScript engine.
Startup¶
On startup, the butler will check for installed brain packages in the Domogik package directory. Depending on the configured language, it will load the appropriate language related files.
Interacting with the butler¶
To interact with the butler, you just need to :
- publish your queries in an interface.input MQ message.
- subscribe for responses or notifications to an interface.output MQ message
interface.input¶
Example :
{
"text" : ... ,
"media" : ... ,
"location" : ... ,
"identity" : ... ,
"sex" : ... ,
"mood" : ... ,
"source" : ... ,
}
Only text, media and source (interface-name.hostname) are mandatory.
interface.output¶
Example :
{
"text" : ... ,
"media" : ... ,
"location" : ... ,
"identity" : ... ,
"sex" : ... ,
"mood" : ... ,
"reply_to" : ... ,
}
The reply_to filed is filled with the source value of an input message.
Core component : dbmgr¶
Purpose¶
The dbmgr component is used to do some actions on the database over the MQ. It allows to :
- get and set configuration parameters values for the plugins
- get the devices list for a client
Core component : manager¶
Purpose¶
The manager component is installed on the main host of a Domogik system. It manages the clients list and details, start the clients (plugins installed on the hosts), ... On Domogik startup, the manager will try to start the other core components (excepting the administration user interfaces).
Packages discovery¶
To find which clients are available on the host, the manager will check for the installed packages on startup, and then each N seconds. If a package in find in the packages installation folder, /var/lib/domogik/domogik_packages/ by default, the manager will check if this is a valid package and if so, it will add this package installed on the host to the clients list.
Core component : xplgw¶
Purpose¶
The xplgw component (xPL gateway) is the gateway between Domogik and xPL world. This component is listening the xPL network for sensor values and send xPL commands over xPL when needed.
How are sensor values caught ?¶
For each device feature a listener is created based on the content of the tables core_xplstat and core_xplstat_param. When the listener catch a message, it will extract the values from it, apply some transformations (history cleanup, round, ...) and store them in database. Some conversions may be applied depending on the device features.
How a xPL command is sent ?¶
When a user interface (or scenario, ...) need to send a xPL command, the following actions are done:
- the REST url /cmd is called with the appropriate parameters (for user interfaces).
- REST send a message on the MQ about the command request.
- xplgw catch the message on the MQ and build the xPL message to send from the parameters it get over the MQ and the contant of the tables core_xplcommand and core_xplcommand_param*.
Domogik data types¶
Purpose¶
Domogik data type are used to define the availables values for a device type.
Where can I find the list of the data types ?¶
The data types allowed by Domogik are defined in the file src/domogik/common/datatypes.json in the sources. Here is for example the content of the file for the *master* branch
How to choose a data type ?¶
If the device can be used for various use cases, you should choose a parent type, for example DT_Bool for a True/False usage. If the device is specific to an usage, you can use a child type, for example, instead of DT_Bool, use DT_Switch for a light, DT_OpenClose for a door sensor.
Notice about widgets¶
A widget should handle both the parent and child type to be compliant with most of the plugins : an on/off light widget would handle both DT_Bool and DT_Switch.
Manager Overview¶
Purpose¶
The manager is the one which will start, stop and monitor all the others components (dbmgr, rest, plugins, ...). It will also manage the packages (installation, ...).
You can have several managers on a multihost installation.
You can call it with the dmg_manager command.
Design¶
The plugins manager is a xPL plugin (inherited of domogik.xpl.common.plugin.XplPlugin). When it starts, it connects to the xPL network and wait for messages.
Depending on the options passed at startup, it will be able to start:
-h, --help show this help message and exit
-d Start database manager if not already running.
-r Start scenario manager if not already running.
-p Activate background ping for all plugins.
-E This manager is the one who looks for hardware.
-t CUSTOM_PING_DURATION
Time for xpl ping duration (default : 10)
-w WAIT_TIME_BETWEEN_PING
Time between 2 xpl ping (default : 15)
-V, --version Display Domogik version.
-f Run the plugin in foreground, default to background.
It will then wait and listen for domogik.system and domogik.package xPL command messages.
Auto-refresh the enabled plugins list¶
When the Domogik configuration file is updated, the manager reloads it (so there is no need to restart the manager after enabling a plugin).
xPL commands¶
See the dedicated page.
Multi host¶
On a multi host system, there are 2 sorts of managers :
- the master which is only on the main host.
- the secondaries which are on all the others hosts.
Master manager¶
On startup this manager will start dbmgr, rest and the plugins. It will also look for external members.
Secondaries managers¶
These managers only handle the plugins on the host (start, stop, monitor, installation). You must not add these options to a secondary manager : -d, -r or -H !!
MQ Overview¶
Library¶
For the Message Queue domogik is using the 0MQ (zero MQ) library. More info about this system can be found at http://www.zeromq.org/.
Publish Subscribe MQ messages¶
Frame format¶
- msg id frame
- data = the data (json format) for the action
The message id¶
The message id is build from a couple of parts
- category = string to subscribe to
- timestamp = str(time()).replace(‘.’,’_’)
- version = 0_1
example:
- category = plugin.status
- timestamp = 1371476851_54
- version = 0_1
plugin.status.1371476851_54.0_1
The content¶
The content is a json encoded python dict
The Network¶
As domogik has a lot of different components that need to publish or subscribe messages a system is required to forward the messages.
The dmg_forwarder was build for this, the forwarder listens on 2 different ports, on for the publisher clients and one for the subscribed clients. So all clients (domogik components) that publish a messages will need to connect to the publisher listing port, the forwarder will then broadcast this message to all clients (domogik components) that are connected on the subscriber port.
The subscribing to certain messages is done on the client side, by using the subscribe socket of zmq.
The publisher¶
The MQPub class is used to publish messages onto the pub/sub network.
- The class needs 2 parameters on init:
- context = is an instance of zmq.Context()
- caller_id = is name used to identify the client
- For sending (publishing) messages there is a send_event method with 2 parameters:
- category = used to generate the message id
- content = a python object (mostly a dict) that well be json encoded to be used as the content of the message
The subscribers¶
Sync client¶
The MQSyncSub class is used to subscribe to certain messages on the pub/sub network.
- The class needs 3 parameters on init:
- context = is an instance of zmq.Context()
- caller_id = is name used to identify the client
- category_filters = a python list with the messages to subscribe to (string match on the beginning of the message id)
Then there is a blocking method called wait_for_event, this method will block until a message is received that matches one of the category_filters.
aSync client¶
The MQAsyncSub class is used to subscribe to certain messages on the pub/sub network.
The class needs 3 parameters on init:
- context = is an instance of zmq.Context()
- caller_id = is name used to identify the client
- category_filters = a python list with the messages to subscribe to (string match on the beginning of the message id)
The on_message callback will be called when a message is received that matches one of the category_filters. The on_message callback has 2 parameters:
- msg_id = is the message id of the message
- content = a python object (probably a dict) that represent the json decoded message content
Request Reply MQ messages¶
The system is based on mdp (http://rfc.zeromq.org/spec:7)
Frame format¶
null frame
protocol (MDPC01 or MDPW01) = what protocol to use (Client or Worker)
- service = the service where we want the request to (rinor, dbmanager, host.pluginname, host.manager)
- for services that can only run once (rest, dbmanager, scenario manager) its just there name
- for services that can run multiple times (on multiple hosts) its in the format <host>.<service>
action = the action to perform (config.get, config.set, plugin.start, plugin.stop, plugin.install, ...)
data = the data (json format) for the action
Clients¶
There are 2 clients provided, an async and a sync client
Async¶
The async object is supposed to be used as an extension of another class, the on_mdp_message method can then be overloaded.
Sync¶
import zmq
from zmq.eventloop.ioloop import IOLoop
from domogik.mq.reqrep.client import MQSyncReq
from domogik.mq.message import MQMessage
cli = MQSyncReq(zmq.Context())
msg = MQMessage()
msg.set_action('plugin.start.do')
msg.add_data('id', 'diskfree')
print cli.request('manager', msg.get(), timeout=10).get()
Workers¶
- all plugins => for helper commands
- dbmanager
- packagemanager
- manager => start/stop a plugin for example
anything that needs to act as req/rep worker can extend the MQRep class
the mdpworker needs to be initialized: MQRep.init(self, zmq.Context(), ‘dbmgr’) the first argument is the zmq context the second argument is the workers name
then you can override the on_mdp_request method def on_mdp_request(self, msg) the msg argument is an object of type MQMessage
the MQMessage has 2 main items: - the action field - the data file (python dict)
How to Use¶
from domogik.mq.reqrep.worker import MQRep
from domogik.mq.message import MQMessage
from zmq.eventloop.ioloop import IOLoop
import zmq
class Rep(MQRep):
def __init__(self):
MQRep.__init__(self, zmq.Context(), 'test_rep')
IOLoop.instance().start()
def on_mdp_request(self, msg):
# display the req message
print(msg)
# call a function to reply to the message depending on the header
if msg.get_action() == "foo.this.do":
self._mdp_reply_foo_this(msg)
def _mdp_reply_foo_this(self, msg_received):
# get the value for a key of the received message
the_value = data.get_data('a_key')
# send the reply
msg = MQMessage()
msg.set_action('foo.this.result')
msg.add_data('another_key', 'another_value')
print msg.get()
self.reply(msg.get())
Examples¶
Example plugin requests config on dbmanager¶
=> only the bold part is seen by the domogik components
client sends the following message:
empty
MDPC01
dbmanager
**config.get**
**{ host = igor, plugin = velbus, key = protocol}**
the dbmanager will then receive the following packet:
Null
MDPW01
0x02
envelopp (used to generate the reply
empty
**config.get**
**{ host=igor, plugin = velbus, key = protocol}**
the dbmanager then does its action and will sent the following packet:
Null
MDPW01
0x03
envelop (copy from the receiving packet)
empty
**config.result**
**{ data }**
the plugin will on its turn receive the following reply:
empty
MDPC01
dbmanager
**config.result**
**{ data }**
Packages overview¶
Purpose¶
In Domgik world a package is a category of module. There are packages for Domogik but also packages for Domoweb (icons, themes for example).
Currently there is only one package type in Domogik: the xPL plugins, called as plugins.
In Domogik 0.5, the only way to install a package is a command line tool : see the user documentation for more inforations.
In the next releases, there will be, as it is in Domogik 0.3, a way to install and find packages over the administration web interface.
Packages and clients¶
A package is just a group of files you can install on a Domogik system. When a package is installed on a Domogik server, it becomes a client : a client is a package instance on a server.
This is a very important thing to understand : a client is a package installed on a server, so you can have a package installed several times on a Domogik multi host system and all these installations are clients.
xPL plugins¶
Purpose¶
xPL plugins packages, called also plugins are the first type of packages available on Domogik. They are used to interact with home automation devices or online services.
How to develop a plugin ?¶
Just read the documentation on how to develop a plugin.
Plugins technical documentation¶
Overview¶
Plugins developers don’t need to read this page to be able to develop some plugins. This page is mainly for core developers to understand internal features of the plugins.
Plugins status¶
During its life, a plugin will get various status. All these status are available:
- STATUS_STARTING : the client is started but not yet ready (waiting for configuration or still doing some init stuff)
- STATUS_NOT_CONFIGURED : the client is not configured and so can’t be started
- STATUS_ALIVE : the client is ready for use
- STATUS_STO_REQUEST : the client has been requested to stop itself
- STATUS_STOPPED : the client as been stopped in the appropriate way
- STATUS_DEAD : the client has disappeared
- STATUS_UKNOWN : the client is in an unknown status (client detected but status not set to anything : for a plugin it may be a syntax error)
- STATUS_INVALID : the client is invalid (for a plugin, this may be an invalid json file)
To get the associated values, please check in Domogik sources : src/domogik/xpl/common/plugin.py
/cmd - send commands to devices¶
Purpose¶
The /cmd entry in REST is used by User Interfaces to send commands to devices.
For a xPL plugin, the translation of the url in a xPL message is done based on the database content (the device feature options) and the info.json file of the plugin.
Url description¶
http://ip:port/cmd/<command id>?<key1>=<value1>&<key2>=<value2>&...
The goal is to be able to generate a xpl message based on a simple url.
The url is formatted like this :
- the first param is command id, this is a defined command in the DB
- the options are corresponding to the parameters of the command, for example the status to apply to a light (on, off) or the dim level to apply to the light. The values that can be used for a device feature are related to the device feature data type.
How the UI will get the status/value of the device feature ?¶
REST only sends http code as response. If http code = 200, then the user interface should request REST to get the last sensor value (a command is always linked to a sensor in the info.json file). To get (for example), the level of a dimmable device after a “+10” increasing command, UI will use /sensor url to get the last value.
How do I find the command id ?¶
To find the command id of a device command, you first need to query the /device url and parse the commands part.
I am a user interface developer and I don’t understand the values to use for the parameters in the url options!¶
To help you to understand with real examples, or to check you are building your /cmd urls in the correct way, you can go in Domogik admin and check the details of a device. You will see all the /cmd available urls for the commands and all /sensor available urls for the sensors.
Which values should be used ?¶
You device is related to a data type. Depending on the data type, you will be able to use some values.
Example 1 : DT_Switch¶
The data type DT_Switch allows these values:
DT_Switch
labels:
1 = On
0 = Off
The related part in the json file which describes the data types is:
"DT_Switch": {
"labels": {
"0": "Off",
"1": "On"
},
"parent": "DT_Bool",
"childs": []
},
So, the available values that can be used in the url are 0 and 1. The labels on and off are used only for display in the user interface : the button to send the value 0 will be named as off.
Note
Notice that is 0 and 1 are the values of the /cmd url, these values can be translated to something else (off/on, low/high, ...) in the generated xPL message. More informations in the conversion chapter.
The goal of the /cmd url is really to be the same for all devices features of the same data type! If the xPL messages are not the same for 2 plugins which use the same data type, this is handled by the plugins thanks to the conversions.
Example 2 : DT_State (which is a boolean data type as DT_Switch)¶
Here is the related json part for the data type:
"DT_State": {
"labels": {
"0": "Inactive",
"1": "Active"
},
"parent": "DT_Bool",
"childs": []
},
As you can see, the only difference with the DT_Switch are the labels values.
Examples¶
Todo
Add more examples
The following data is in the DB:
Device¶
id = 1
name = test
usage = ventilation
type = x10.relay
Commands¶
id = 10
name = command1
xpl_command_id = 12
Command params¶
id = 11
key = level
Xpl command¶
id = 12
device = 1
schema = lighting.device
Xpl command param¶
command = 12
key = address
value = 12
Now if we call the ‘/cmd/10/level/100 we will generate the following xpl command:
lighting.device {
level = 100
address = 12
}
This example demonstrates how the url can generate any type of xpl message, the number of command params or xpl command params is unlimited.
The commands are dynamic parameters and need to be sent via the url, the xpl command params are staticly defined, these definitions are done during device creation and are not changeable afterwards.
REST Overview¶
Todo
- description
- pictures/charts about startup, url processing, MQ dialogs
- ...
Overview¶
REST is a web server which handle request in REST format. It is used by the user interfaces to get informations about devices, sensor history, send commands, ...
How to test REST requests¶
wget method¶
You can execute a REST GET request from a console using the wget command. For example you can run:
$ wget -qO- http://127.0.0.1:40405/device/1
This will run the request and show you the results (thanks to the -qO- parameter).
FireFox RESTClient¶
This Firefox plugin will allow you to send easily POST, DELETE and other requests.
Install FF plugin from here: https://addons.mozilla.org/en-us/firefox/addon/restclient/.
Lauch with Tools > RestClient
In this example we will use RESTClient for testing POST url
In the RESTClient interface menu go in Headers > Custom Header and :
- Create a header with name: ‘Content-Type’, value: ‘application/x-www-form-urlencoded’
- You may tick Save to favorites for the next time
Next, go back on the RESTClient main interface and fill the fields. Example for creating a new device:
- method: ‘POST’, url: ‘http://xxxxx:40405/device/‘
- body : name=device%20name&type=plugin&id=diskfree&host=darkstar&description=desc&reference=ref&device_type=diskfree.disk_usage
Then, click on Send
Configuration section in /etc/domogik/domogik.cfg¶
In Domogik configuration file, there are several options about REST in [rest] section:
- interfaces : the network interface to use : eth0, lo, ...
- port : the port for the rest service : 40405
- use_ssl : use ssl (True) or not (False)
- ssl_certificate : The ssl .crt file
- ssl_key : The ssl .key file
- clean_json : display a clean json result (indentation, carriage return) (True) or a raw result (False)
How to use REST with SSL?¶
Please read the installation documentation.
Status¶
Rest uses http status codes to identify the error:
- get:
code = 200
data = the returned data from the get request (json)
delete:
code = 204
data = empty
put (update):
code = 200
data = the updated object (json)
post (create):
code = 201
data = the new object (json)
error:
code = 400
data = {msg: “string describing the error”}
action ok, no content returned:
code = 204
data = Empty
Internal domogik xpl messages¶
Actually Domogik uses some custon xPL schema for the communication between the components. They will be replaced by a message queue soon. You can find some informations about them on the wiki:
plcbus.basic¶
Purpose¶
No official xPL Schema exists for Plcbus. A dedicated schema has been made for this feature. Discussion on the official xPL Forum about this xPL schema: http://xplproject.org.uk/forums/viewtopic.php?f=2&t=908&
plcbus.basic Message specification¶
- Class = PLCBUS
- Type = BASIC
xpl-cmnd¶
This sends a command to the plcbus plugin. The usercode used is always the same in one house (it only changes if we manage a plcbus system in many houses near from each others). ACK may be ignored if not usable with the defined COMMAND.
PLCBUS.BASIC
{
USERCODE=<plcbus network usercode>
DEVICE=<sensor name>
COMMAND=<value sent to the device>
[ACK=<0|1>]
[DATA1=<additional data>]
[DATA2=<additional data>]
}
Todo
- explain more Usercode and Device (with examples)
- check if ACK is used or not
- list of available COMMAND values
- DATA1 and DATA2 : description of usage
xpl-stat¶
It is used to send a message when a command is received from the plcbus network (for ex, is sent by a remote).
PLCBUS.BASIC
{
USERCODE=<plcbus network usercode>
DEVICE=<device name>
COMMAND=<value sent to the device>
[DATA1=<additional data>]
[DATA2=<additional data>]
}
xpl-trig¶
This is used to send an acknowledge when an ack command is received or when an event is raised.
PLCBUS.BASIC
{
USERCODE=<plcbus network usercode>
DEVICE=<device name>
COMMAND=<value sent to the device>
ACK=1
[DATA1=<additional data>]
[DATA2=<additional data>]
}
Examples¶
Find below manual commands examples, assuming your usercode is “FF”:
Turn ON A1:
dmg_send xpl-cmnd plcbus.basic "device=A1,command=ON,usercode=FF"
Turn OFF A1:
dmg_send xpl-cmnd plcbus.basic "device=A1,command=OFF,usercode=FF"
Preselect Dimmer to 40% on A1:
dmg_send xpl-cmnd plcbus.basic "device=A1,command=PRESET_DIM,usercode=FF,data1=40"
Preselect Dimmer to 40% with 3 secondes Fade Rate on A1:
dmg_send xpl-cmnd plcbus.basic "device=A1,command=PRESET_DIM,usercode=FF,data1=40,data2=3"
Preselect No Fade Rate on A1:
dmg_send xpl-cmnd plcbus.basic "device=A1,command=PRESET_DIM,usercode=FF,data2=0"
Turn ALL Ligths OFF:
dmg_send xpl-cmnd plcbus.basic "command=ALL_LIGHTS_OFF"
Turn ALL Ligths ON:
dmg_send xpl-cmnd plcbus.basic "command=ALL_LIGHTS_ON"
Teleinfo xPL messages¶
Purpose¶
There is no xPL schema defined for the information of a (french) teleinfo system. This page defines this needed schema.
Teleinfo.basic Message specification¶
- Class = TELEINFO
- Type = BASIC
The teleinfo.basic schema is used to send the data of a teleinfo system from an electric counter.
xpl-trig¶
There is no xpl-trig message for this schema
xpl-cmnd¶
There is no xpl-trig message for this schema
xpl-stat¶
Note
As this is only a French schema, the keys description is in french
This is the classic message. Notice that the device identifier corresponds to the ADCO field:
teleinfo.basic
{
adco=<Adresse du compteur>
optarif=<Option tarifaire>
isousc=<Intensité souscrite>
base=<Index option base>
iinst=<Intensité instantanée>
imax=<Intensité maximale appelée>
motdetat=<Mot d'état du compteur>
[hchc=<Heures|creuses>]
[hchp=<Heures|pleines>]
[ejphn=<Heures|normales>]
[ejphpm=<Heures|de pointe>]
[bbrhcjb=<Heures|creuses jours bleus>]
[bbrhpjb=<Heures|pleines jours bleus>]
[bbrhcjw=<Heures|creuses jours blancs>]
[bbrhpjw=<Heures|pleines jours blancs>]
[bbrhcjr=<Heures|creuses jours rouges>]
[bbrhpjr=<Heures|pleines jours rouges>]
[pejp=<Préavis|début EJP (30min)>]
[ptec=<Période|tarifaire actuelle>]
[demain=<Couleur|du lendemain>]
[adps=<Avertissement|de dépassement>]
[papp=<Puissance|apparente>]
[hhphc=<Horaire|heure pleine/heure creuse>]
[ppot=<Présence|des potentiels>]
[iinst1=<Intensité|instantanée phase 1>]
[iinst2=<Intensité|instantanée phase 2>]
[iinst3=<Intensité|instantanée phase 3>]
[imax1=<Intensité|maximale phase 1>]
[imax2=<Intensité|maximale phase 2>]
[imax3=<Intensité|maximale phase 3>]
[pmax=<Puissance|maximale triphasée>]
}
Teleinfo.short Message specification¶
- Class = TELEINFO
- Type = SHORT
The teleinfo.short schema is used when the max intensity is reached on a 3 phasis installation.
xpl-trig¶
There is no xpl-trig message for this schema
xpl-cmnd¶
There is no xpl-trig message for this schema
xpl-stat¶
This is the message sent when for a 3 phasis installation, the max intensity is reached. Notice that the device identifier corresponds to the ADCO field:
teleinfo.short
{
adir1=<Dépassement d'intensité sur la phase 1>
adir2=<Dépassement d'intensité sur la phase 2>
adir3=<Dépassement d'intensité sur la phase 3>
adco=<Adresse du compteur>
iinst1=<Intensité instantanée phase 1>
iinst2=<Intensité instantanée phase 2>
iinst3=<Intensité instantanée phase 3>
}
weather.basic¶
Actually, find all the informations on the wiki : http://wiki.domogik.org/Plugin_weather_recommendations
The brain file tree¶
The brain file tree will at least contains (assuming the brain name is mybrain):
- __init__.py : an empty file needed by python
- info.json : the json file which describe the brain and its features. This file must always be named like this!
- rs/<lang>/*.rive : the rivescript files
- doc/ : the sphynx documentation in ReST format
- design/ : this folder will contain all graphical resources (icons, ...)
- design/icon.png : the brain icon (png, 96px * 96px). This file must always be named like this!
Some other items may be added:
- data : if needed this folder can contain data needed by the brain or the brain may write data in it
- tests : if the brain have some tests scripts, they must be here
- lib/ : the python libraries
- lib/__init__.py : an empty file needed by python
- lib/mybrain.py : the python library part
Dedicated administration pages¶
Purpose¶
The dedicated administration pages are available in Domogik administration on a client page in the Advanced menu.
How it works¶
On startup, Domogik administration will look for each installed client if there is an admin/ folder. If so, the content of this folder will be handled by Flask to be available from the administration.
So, you just need to fill an admin/ folder in the root of your package repository.
Create a simple page (diskfree example)¶
First, create the files :
mkdir admin/
mkdir admin/templates/
touch admin/__init__.py
touch admin/templates/plugin_diskfree.html
Of course, change plugin_diskfree with the appropriate package id.
admin/__init__.py file¶
The __init__.py file will NOT be empty. It will contain the Flask related python code needed by Domogik administration to use your dedicated pages.
Example code (plugin diskfree) :
# -*- coding: utf-8 -*-
### common imports
from flask import Blueprint, abort
from domogik.common.utils import get_packages_directory
from domogik.admin.application import render_template
from domogik.admin.views.clients import get_client_detail
from jinja2 import TemplateNotFound
### package specific imports
import subprocess
### package specific functions
def get_df():
df = subprocess.Popen(["df", "-h"], stdout=subprocess.PIPE)
output = df.communicate()[0]
#device, size, used, available, percent, mountpoint = output.split("\n")[1].split()
if isinstance(output, str):
output = unicode(output, 'utf-8')
return output
### common tasks
package = "plugin_diskfree"
template_dir = "{0}/{1}/admin/templates".format(get_packages_directory(), package)
static_dir = "{0}/{1}/admin/static".format(get_packages_directory(), package)
plugin_diskfree_adm = Blueprint(package, __name__,
template_folder = template_dir,
static_folder = static_dir)
@plugin_diskfree_adm.route('/<client_id>')
def index(client_id):
detail = get_client_detail(client_id)
try:
return render_template('plugin_diskfree.html',
clientid = client_id,
client_detail = detail,
mactive="clients",
active = 'advanced',
df = get_df())
except TemplateNotFound:
abort(404)
First, there are all the imports. You should customize only the package specific imports
part. Then, the function get_df()
is specific to this plugin, you can, like here define as many functions as needed.
The ### common tasks
part is important : you must customize the package
variable. Then, you have to create a Blueprint object named like this : <package type>_<package name>_adm, in our example : plugin_diskfree_adm
.
After, you will have to create one function for each administration page you need. Each function will at least contains the same actions as the example.
admin/templates/*.html¶
You must at least have one template file. This mandatory file must be named <package type>_<package name>.html, for example: plugin_diskfree.html
. Each file you will have to create must start by the prefix <package type>_<package name>. This allow avoiding collisions between the packages dedicated pages.
Here is an example for the plugin diskfree :
{% extends theme("base/base.html") %}
{% import theme("base/wtf.html") as wtf %}
{% block content %}
{% include theme("client_menu.html") %}
<div class="container">
<h2>{% trans %}Result of the 'df -h' command{% endtrans %}</H2>
<p>{% trans %}You only need to create devices for the mounted pathes. The <em>df -h</em> result will show you all the mounted pathes that may be monitored.{% endtrans %}</p>
<p>{% trans %}Please notice that the plugin may not give exactly the same values! This is related to the way the size are calculated. {% endtrans %}</p>
<pre>{{ df }}</pre>
</div>
{% endblock %}
You are free to put anything you want/need in the container div. But you need to keep all the others lines as given.
Use Domogik Message Queue (MQ)¶
If you need to use the MQ from your admin page, you can by using WebSockets in Javascript : a gateway is available in the admin.
Todo
Examples
Conversion functions¶
Domogik is using a fixed data format (Data types <data_types/index>) to store sensor data in the database and to generate the xpl commands. Some hardware can not work directly with this data format and need some sort of conversions. For the above purpose Domogik introduced Conversion functions, these are very simple python functions that input a value and need to return a single value. These python functions are called in 2 places inside Domogik. 1- During xpl command generation: The input value is coming from REST or MQ and is then placed inside the transmitted XPL message. 2- During stat receival: The incoming xpl value is used as the parameter of the conversion function and the return value is stored in the database.
Limitations¶
Because the conversion code is using eval() the conversion code in the file should be very simple. There should be no comments in the file and there should only be one function per conversion file. See the example below.
Example¶
Some conversion may be needed for some features of a plugin. For example, the velbus plugin has some level features. Velbus hardware handle these levels with values from 0 to 255. For such features, Domogik has a datatype named DT_Scaling which allows a range from 0 to 100. So when data is sent from the velbus plugin over xPL, the data is in the range [0, 255]. When the xplgw component received it, before storing it in database, it will convert the value for the needed Domogik datatype : from [0-255] to [0-100]. To allow this, the plugin is delivered with a file from_level_to_DT_Scaling.py which contains:
def from_level_to_DT_Scaling(x):
# 0 - 255 translated to 0 - 100
return round(int(x) / 255 * 100)
This conversion function is used by a sensor, defined in the info.json file:
"sensors": {
...
"level_range": {
"name": "level sensor",
"data_type": "DT_Scaling",
"conversion": "from_level_to_DT_Scaling",
"incremental": false,
"history": {
"store": true,
"duplicate": false,
"max": 0,
"expire": 0,
"round_value": 0
}
},
...
So, each time a value is caught by xplgw for such a sensor, it will be translated thanks to the conversion function to the appropriate value.
Create a repository for a plugin¶
Create the repository¶
Now the plugins sources are no more present in Domogik sources. Each plugin has its own GitHub (or elsewhere you want) repository. Thanks to this, everybody can now create its own plugin with version control without needing to request the Domogik team for an access.
You must name the repository like this : domogik-<type>-<name> :
- domogik : this will allow everybody to find easily your plugin.
- <type> : the package type. For a plugin, the package type is plugin.
- <name> : the package name. It will be your plugin name : onewire, ipx800, plcbus, .... The package name must have a max length of 8 characters and be lower case. If needed you can use an underscore in the name. This limit is related to the xpl protocol.
Go on http://github.com, log in and create an account by clicking on the icon highlighted in yellow :

You will access to the repository creation form :

Fill the repository name, let the access as Public, set .gitignore to None (we will create our own file) and click on Create repository.
The repository is now created:

Clone the repository to start developing your plugin¶
In the right column, you can easily copy a link to clone the repository. To clone over ssh, just click on the SSH link:

And then, click on the icon highlighted in yellow to get the url in your clipboard:

You will have something like : git@github.com:fritz-smh/domogik-plugin-diskfree.git
Let’s say you will work in you home directory. Clone your repository:
$ cd $HOME
$ git clone git@github.com:fritz-smh/domogik-plugin-diskfree.git
If you want, you can activate the plugin on Domogik side:
$ cd /var/lib/domogik/domogik_packages
$ ln -s /home/youruser/domogik-plugin-diskfree plugin_diskfree
Notice that you could wait for your plugin to have a valid json first and then use the dmg_package tool to install it :
$ dmg_package -i $HOME/domogik-plugin-diskfree
Assuming you cloned an already working plugin, Domogik will automatically detect the plugin after a few seconds and you will be able to configure it and start it.
Be carefull : the symbolic link must be named like this : /var/lib/domogik/domogik_packages/[package type]_[package name]! You must use an underscore because python will not be able to see your plugin as a package is you use a minus.
What should contain the documentation ?¶
The documentation is very important! If there is no documentation for your plugin or if the documentation is not clear, the users won’t want to use your plugin.
A structured documentation¶
Your documentation must be logic and structured. It should follow some logical steps:
- first, describe the plugin, what is its goal, what are its features
- then, don’t forget to list the compliant hardware devices or services
- add some photos of the hardware in order the user can be sure he has the same thing
- if there are some complex dependancies (like OWFS for onewire), explain how to install them or at least give url to the appropriate documentations
- explain how to configure the plugin (the global configuration, not the domogik devices configuration)
- explain how to create a device (not where the user should click on the user interface as there is already a documentation for this) :
- which feature it used for which hardware device or service ?
- what does a device address will look like
- give sample values and explain them
- if your plugin has some helpers, explain their features and how to use them
- finally you may add any informations that seem important for the user
Doc for developers¶
You must create a documentation part about the plugin development. In this part, you must at least:
- list and describe the xPL schema used, with some examples.
Documentation for plugins¶
The documentation of a plugin must be included in the docs/ folder of the plugin. The documentation is written in reStructuredText format and will be build with sphinx.
Thanks to sphinx, you can choose the way (and url) to generate your plugin documentation. But we suggest you to use the ReadTheDocs.org <http://readthedocs.org> (RTD) online service. It offers several useful features:
- documentation is hosted by RTD
- you can easily manage separate documentation for each version of your plugin
- documentation can be build on each commit pushed on a Github repository
- the built documentation is really clear and easy to read
Publish your documentation with ReadTheDocs.org¶
Prerequisites¶
Your doc is written :)
Prepare the configuration file for Sphinx¶
In the plugin repository, create a file docs/conf.py which contains:
import sys
import os
extensions = [
'sphinx.ext.todo',
]
source_suffix = '.txt'
master_doc = 'index'
### part to update ###################################
project = u'domogik-plugin-diskfree'
copyright = u'2014, Fritz'
version = '0.1'
release = version
######################################################
pygments_style = 'sphinx'
html_theme = 'default'
html_static_path = ['_static']
htmlhelp_basename = project
Update the middle part depending on your plugin.
Create an account on RTD¶
If you don’t have an account, go on RTD and create an account
Add your plugin project to RTD¶
In the dashboard <https://readthedocs.org/dashboard/>, click on Import to add your plugin documentation.
Fill the form:
- Name : put the full plugin name : domogik-plugin-myplugin.
- Repo : put the github repository url : http://github.com/mygithublogin/domogik-plugin-myplugin.git
- Repository type : Git or something else if you don’t use git
- Description : your plugin description
- Language : English
- Documentation type : Sphinx html
- Project url : empty
- Canonical url : empty
- Tags : domogik
Click on Create.
The build will be automatically started for the branch master. It may fail with the reason:
Conf file not found.
This is normal as the master branch contains no documentation (and no plugin code) if you are working on the first release of your plugin!
You will now need to activate the develop branch for your RTD plugin project.
Generate documentation for the develop branch¶
From the dashboard, for your plugin project, click on the Admin button. In the menu, go in Versions.
Set the develop version as Active. Uncheck the Active checkbox for the latest version and click on Submit.
You are now on the Overview page for your plugin project. The only version available is develop. You can click on Build to start the build of the documentation for the develop branch.
In the Builds menu for your project, you can check if the build was successful or not. If not, you can click on the status to get the error details.
You can now see the built documentation at http://domogik-plugin-myplugin.readthedocs.org/en/develop/ (adapt myplugin to your plugin name)
Notice that the View Docs button will send you to the latest version of your plugin documentation, which does not exists yet! This version will exists when your plugin will be stable and so, when you will have merge the develop branch in the master branch.
Set the hook to build the doc for each commit pushed¶
In your github project, you can set a hook to launch the documentation build on RTD for each commit pushed.
Go on your Github project url. For example https://github.com/mygithublogin/domogik-plugin-myplugin
- Go in the Settings menu, then in Webhooks and services.
- In the Services part, click on Add service and choose ReadTheDocs.
- The ReadTheDocs service configuration page will be displayed. Check that Active is set and click on Add service.
Now, just do a documentation update, commit it and push it. You should see the doc updated a few minutes later.
Versions management¶
- ..todo ::
- Continue :) * version management
i18n¶
- ..todo ::
- TODO !
Read the doc :)¶
Your documentation is available at http://domogik-plugin-<plugin name>.readthedocs.org/en/<branch name>/. Example : http://domogik-plugin-teleinfo.readthedocs.org/en/develop/
Writing documentation for sphinx in ReST format¶
Documentation tree¶
Depending on the plugin, the documentation may be very big (this is the case for the plugin ozwave for example) or very small (this is the case for the plugin diskfree for example). So you won’t create your documentation in the same way for all the plugins. In this chapter, we will give you only rules and recommendations.
The mandatory part¶
Only 3 files are mandatory:
- docs/index.txt
- docs/myplugin.txt : replace myplugin by your plugin name : diskfree, ozwave, ...
- docs/changelog.txt
- docs/dev.txt
The index.txt file will be the toctree of your documentation. It will contain only references to all the other files.
The myplugin.txt file is the main file for your plugin documentation. It will contain informations about plugin configuration, devices creation, ...
The changelog.txt file will contain the history of your package.
The dev.txt will contain some technical informations for the developers.
Example tree for a simple plugin¶
For a plugin named myplugin:
docs/index.txt
docs/myplugin.txt
docs/dev.txt
docs/changelog.txt
docs/*.png
For these plugins all the content of the user documentation will be directly written in myplugin.txt.
Example tree for a big plugin¶
docs/index.txt
docs/mybigplugin.txt
docs/aspecialpage.txt
docs/anotherspecialpage.txt
docs/dev.txt
docs/changelog.txt
docs/*.png
Of course you can use sub directories if you need.
Focus on index.txt¶
The index.txt file is very important! It must contain a link to all other txt files. Example:
.. _toc:
================
Table Of Content
================
.. toctree::
/myplugin
/dev
/changelog
Domogik field types¶
Purpose¶
These types are used to validate the input (on the ui) for certain fields. An example configuration parameters, command parameters, ....
Known Field Types¶
Boolean¶
Will result in a checkbox, value can be True or False
String¶
Wil result in a text input field
- optional parameters:
- max_length : integer
- min_length : integer
- mask_str : regexp to validate the string
- multiline : boolean
Password¶
Wil result in a password input field
- optional parameters:
- max_length : integer
- min_length : integer
- mask_str : regexp to validate the string
Date¶
A format of ‘DD/MM/YYYY’
Time¶
A format of ‘HH:MM:SS’
DateTime¶
A format of ‘DD/MM/YYYY HH:MM:SS’
Email¶
An email format
Ipv4¶
An ipv4 address
Ipv6¶
An ipv6 address
Git¶
.gitignore file¶
For each plugin, a .gitignore file must be created in the root directory. Create it like this:
$ echo "*.pyc
*.swp
_build_doc" > .gitignore
This file is used by git to exclude some files from the repository.
In git what is a branch ?¶
If you don’t know what are branches in git, please read this link : http://gitref.org/branching/
Package icon¶
A package icon must respect the following rules:
- PNG format
- Size of 96*96 px
- Be GPL compliant (don’t use private or non free existing icons)
The icon files must be store in design/icon.png
Package json file¶
Warning
This documentation is only valid for json version 2 and up. To learn how to upgrade read the json file upgrade documentation
Purpose¶
The Json file describe the package and the features of the package. There is one common part which is the same for all packages type and some optional parts that depends on the package type.
Warning
In json, you must write \n each time you want to create a newline in the data.
The common part¶
Package type : | Plugin | External |
---|---|---|
Is section required for package type : | ![]() |
![]() |
The common part is like this :
Example¶
{
"json_version": 2,
"identity": {
"type": "plugin",
"id": "onwire",
"category": "onewire",
"version": "0.1",
"domogik_min_version": "0.2.0",
"documentation": 'http://wiki.domogik.org/plugin_onewire',
"description": 'Manage 1-wire devices...',
"author": 'Domogik',
"author_email": 'xx@xxx.fr',
"changelog" : "0.1\n-Create plugin",
"dependencies": [
{
"id": "owfs (>=2.8.4)",
"type": "other"
}
],
},
"files": [
"src/share/domogik/design/plugin/onewire/icon.png",
"src/share/domogik/plugins/onewire.json",
...
],
}
Description¶
json_version : version of the json file for this particular element type.
identity : element identity.
type : the type id for the element:
- plugin.
- external.
id : package id (name).
category : package category (for a plugin, it will be its technology).
version : package version.
domogik_min_version : minimum Domogik version required by the package.
description : package description.
author : Name or surname of the developer.
author_email : email the developer.
documentation : link to the specification page.
changelog : changelog. For each version you must indicate the fixes/upgrades.
dependencies : table of dependencies needed by the package :
type : type of the dependency
python : a python dependency (distutils2 format). Ex : ‘pyserial (>=2.5)’, ‘foo (>1.0, <1.8)’, ...
plugin : a Domogik plugin dependency : another plugin must run in order this one could be functional.
other : another dependency (example : owfs for onewire, which need a manual installation).
files : list of the files included in the package.
Optional parts¶
You must add the following parts in the json file when developing a package.
Technology¶
Package type : | Plugin | External |
---|---|---|
Is section required for package type : | ![]() |
![]() |
Example¶
"technology": {
"description": "1-wire",
"id": "onewire",
"name": "1-wire"
},
Description¶
These data will be inserted in database during the package installation.
- technology : description of the package technology
- id : technilogy id.
- name : tehcnology name.
- description : short description of the technology.
Device types¶
Package type : | Plugin | External |
---|---|---|
Is section required for package type : | ![]() |
![]() |
Example¶
"device_types": {
"onewire.thermometer" {
"description": "Thermometer",
"id": "onewire.thermometer",
"name": "Thermometer",
"commands": ["set_level_bin"],
"sensors": ["level"],
"params": [
{
"key": "channel",
"description": "The channel number",
"type": "integer",
},
...
]
},
...
}
Description¶
These data will be inserted in database during the package installation.
device_types : dictionary of device types, indexed on device_type id
id : device type id
name : device type name
description : short description of the device type
commands : list of commands supported by this device type
sensors : list of sensors supported by this device type
- params : list of parameters that are common to ALL xpl messages for this device type
- key : the xpl key for the message
- description : short description of this field
- type : the value type for this field
- depending on the type the are other possibilities (like min_value, max_value, ....)
Configuration elements¶
Package type : | Plugin | External |
---|---|---|
Is section required for package type : | ![]() |
![]() |
These data are read by the manager.
Example¶
"configuration": [
{
"id": "0",
"interface": "no",
"key": "startup-plugin",
"type": "boolean",
"options": [],
"default": "False",
"description": "Automatically start plugin at Domogik startup",
"optional": "no",
},
...
]
Description¶
configuration : list of the configuration parameters of a plugin
id : number of the configuration item. The display order will be related to the id.
interface : yes or no. If yes, group with all the following keys with interface = yes. This group of keys allow to configure N iterations of these keys.
type : the type of the value to set for the key.
string : the default type
number : a number
boolean : True, False
enum : a list of options. Example:
"type" : "enum", "options": [ "ipx800v1", "ipx800pro", "ipx800v2", "ipx800v3" ],
options : if type = enum, list the available options in this table.
default : suggested value.
description : parameter short description.
optional : yes or no : is the parameter optional or not ? It is used only for notification in the user interface.
Todo
detail enum
Udev rules¶
Package type : | Plugin | External |
---|---|---|
Is section required for package type : | ![]() |
![]() |
Example¶
"udev-rules": [
{
"description": "Usb DS9490R adaptator",
"filename": "onewire.rules",
"model": "DS9490R",
"rule": "SUBSYSTEMS==\"usb\", ATTRS{idVendor}==\"04fa\", ATTRS{idProduct}==\"2490\", SYMLINK+=\"onewire\", MODE=\"0666\""
}
]
Description¶
- udev-rules : list of the suggested udev rules. * description : short description of the rule. Indicate the related device model here. * filename : suggested filename to use for the rule. * model : related device model. * rule : the udev rule.
External members identification¶
Package type : | Plugin | External |
---|---|---|
Is section required for package type : | ![]() |
![]() |
Example¶
"external": {
"device_id": "rgb",
"vendor_id": "arduino"
},
Description¶
- external : description of the vendor and device id of the external member. This is used to identify a specific external member model. * device_id : device id. * vendor_id : vendor id.
Commands¶
Package type : | Plugin | External |
---|---|---|
Is section required for package type : | ![]() |
![]() |
Example¶
"commands": {
"set_level_bin": {
"name": "Switch On or Off",
"return_confirmation": true,
"params": [{
"key": "level",
"value_type": "binary",
"values": [0, 255]
}],
"xpl_command": "set_level_bin"
},
...
}
Description¶
- Commands : this section describes the commands needed by this plugin, its a dictionary indexed by the command reference
name : the name of this command
return_confirmation : does rinor need to wait for a confirmation
- params : the needed params to be able to complete this command (typically these are the values set by the widget)
key : the name of the parameter
value_type : what type of value we can receive
- values : the possible values, the format depends on the value_type
- value_type = binary : then values is a list containing the off and on value
- value_type = range : then values contains a list of min and max value of the range
xpl_command : what xpl command is linked to this command (this field is optional)
Sensors¶
Package type : | Plugin | External |
---|---|---|
Is section required for package type : | ![]() |
![]() |
Example¶
"sensors": {
"level": {
"name": "level",
"unit": "%",
"value_type": "range",
"values": [0, 100]
},
...
},
Description¶
- Sensors : this section describes the sensors supported by this plugin, its a dictionary indexed by the command reference
name : the name of the sensor
unit : the unit of this value, needed for ui display
value_type: what type of value we can receive
- values : the possible values, the format depends on the value_type
- value_type = binary : then values is a list containing the off and on value
- value_type = range : then values contains a list of min and max value of the range
Xpl_commands¶
Package type : | Plugin | External |
---|---|---|
Is section required for package type : | ![]() |
![]() |
Example¶
"xpl_commands": {
"set_level_bin": {
"name": "blah",
"schema": "lighting.basic",
"xplstat_name": "get_level",
"parameters": {
"static": [
{
"key": "stat",
"value": "stat"
}
],
"device": [
{
"key": "dummy",
"description": "a dummy param",
"type": "string"
}
]
}
},
...
},
Description¶
- xpl_command : this section describes the xpl_command to sent when a command is triggered
name : the name of the command
schema : the xpl schema to use
xplstat_name : the xplstat that will be expected as a confirmation
- parameters : the key/value pairs for the xpl message
- static : values that are static, these key/value pairs are always there and not changeable
- key : xpl key
- value : xpl value
- device : parameter that can be changed on a per device basis, will be displayed in the create device interface
- key : xpl key
- description : the description that will be displayed
- type : the value type
Xpl_stats¶
TODO
How to insert the data in database¶
This action is only for developers!
If you want to manually insert the data of the json in the database, launch this command :
cd src/tools/packages
./insert_data.py ../../share/domogik/plugins/<your plugin name>.json
info.json for plugins - commands section¶
Purpose¶
The commands section will quickly describe each command with:
- a name
- an xpl command to generate. Sess xpl commands for info about these.
- a list of parameters
Example 1¶
This example is the velbus plugin. See the device types documentation for more informations.
For each command, we define a name and the required parameters for this command.
"commands": {
"set_level_bin": {
"name": "Switch On or Off",
"return_confirmation": true,
"parameters": [{
"key": "level",
"data_type": "DT_Switch",
"conversion": "from_DT_Switch_to_level"
}],
"xpl_command": "set_level_bin"
},
...
}
Description¶
Each item has several properties:
name : it is a string used to display this command on the UI
return_confirmation : true or false depending if the plugin responds to the command
xpl_command : what xpl command to generate
- parameters : a list of parameters that need to be filled in during command generation
- key : the xpl-message key of the parameter
- data_type : what datatype this parameter will be
- conversion : the optional conversion function to run. See conversion function documentation
What happens if a command is received by the xpl gateway¶
- Check that all parameters are provided in the mq message
- Load the needed parameters from the xpl_command
- Combine all parameters to generate the xpl message
- Load the xpl_stat thats linked to the defined xpl command
- Create an xpl listener for the xpl_stat/xpl_trigeer messages
- Send out the xpl_command
- Wait until the xpl_stat is received
info.json for plugins - configuration section¶
Purpose¶
The configuration section describes the plugin configuration items. This part is only related to global configuration ! All the configuration elements which are related to a device are to be defined in the domogik device types part.
Example¶
For this example we assume that your plugin has this configuration item:
- an interval item : if not configured, the default value configured in the json will be used y the plugin
Here is the corresponding section:
"configuration": [
{
"default": 5,
"description": "Interval between each poll (minute)",
"key": "interval",
"name": "Interval",
"required": false,
"type": "integer",
...
}
Notice about the auto_startup parameter¶
One parameter is automatically added by Domogik to your configuration parameters : the auto_startup element. The following data is dynamically added by Domogik when loading the info.json file:
{
"default": false,
"description": "Automatically start the plugin at Domogik startup",
"key": "auto_startup",
"name" : "Start the plugin with Domogik",
"required": true,
"type": "boolean"
}
This element allows the user to enable a plugin to be started on Domogik startup (useful during a server reboot).
Description¶
Each item has several properties:
- key : the configuration key. This is used to store the value in database. The key must be lower case, limited to 255 characters. The only separator allowed in the underscore. This won’t be displayed on the user interface.
- name : a human readable equivalent of the key. This wil be displayed on the user interface.
- description : the configuration item description. If the item type is an integer, a float or something like this, you must specify the unity (minutes, seconds, ...) in the description.
- type : the configuration item type. You will find more informations below.
- default : the default value. If the configuration item is not set in database, this default value will be used by the plugin.
- required : is the configuration of this item required to run the plugin ? On the user interface, you may not be able to start a plugin if all the required = true items are not filled. If the required key doesn’t exist, we assume that the item is required and required = true.
- ... : some additional and optional parameters depending on the type. You will find more informations below.
Notice above special values¶
In json format, you need to use for the boolean type :
- true
- false
For undefined (None in python, undefined in javascript, Null in Java) values :
- null
auto_startup¶
The auto_startup item must be set for all the plugins. This allow the user to configure on the user interface if he wants the plugin to be started on Domogik startup. The default value must be false.
Data types¶
Notice that when retrieving the plugins values for the configuration elements, they are automatically casted in the appropriate format which is the one describe in this part of the json file!
Here are some type examples:
- Boolean
- String
- ...
info.json for plugins - device_types section¶
Purpose¶
The device_types section describes all the Domogik device types that are handled by the plugin. A Domogikdevice type is a sort of device (for example an analog input, a digital input, a temperature sensor, a temperature and humidity sensor, ...).
A device type is linked to some commands and some sensors. For example, a temperature sensor will have only a temperature sensor. A temperature and humidity sensor will have 2 sensors : a temperature sensor and a humidity sensor. In some technologies, an on/off lighting device type will have 1 command : on/off (which may be named switch) and a dimmer device type will have 2 commands: on/off (switch) and dimmer (which could be simply named dimmer).
Finally a device type has some parameters. For simple device type, there will be only one parameter : the device address. For some complex device type (some KNX or Chacon devices for example), you will have several parameters : the address which is split in 2 parts (chacon devices), the actuator address which is not the same as the sensor one (KNX devices), ...
The known datatypes can be found in the file /var/lib/domogik/resources/datatypes.json.
Example 1¶
This example is the diskfree plugin. This plugin has only one device type named diskfree.disk_usage. This device type feature is to look at the disk space status on a filesystem. There are 4 informations which are available for this feature:
- the total space available
- the free space available
- the used space
- the percent of space used
These 4 informations are here given by 4 sensors.
To check these sensors the plugin need 2 information for the device type:
- the path to look at on the filesystem
- the interval between each check
"device_types": {
"diskfree.disk_usage": {
"description": "Disk usage",
"id": "diskfree.disk_usage",
"name": "Disk usage",
"commands": [],
"sensors": ["get_total_space", "get_percent_used", "get_free_space", "get_used_space"],
"parameters": [
{
"key": "device",
"xpl": true,
"description": "The path to look at.",
"type": "string"
},
{
"key": "interval",
"xpl": false,
"description": "The time in minutes between each check.",
"type": "integer"
}
]
}
}
Description¶
- id : this is the device type id
- name : the device type name
- description : a short description of the device type
- commands : the list of commands supported by this device type
- sensors : the list of sensors supported by this device type
- params : list of parameters that are common to ALL xpl messages for this device type
- key : the xpl key for the message
- xpl : is this an xpl parameter or nit, if set to true this parameter will be included in the xplcommands and expected in the xplstats/triggers, if set to false it will only be available internal in domogik or in the plugin if it requests the devices
- description : short description of this field
- type : the value type for this field. The available values are the same as the ones used in the configuration part of the json
- default : Optionally a default value for this parameter, will be pre filled in in the admin interface during device creation
- depending on the type the are other possibilities (like min_value, max_value, ....)
info.json for plugins - identity section¶
Purpose¶
The identity section is the identity card of the plugin.
Example¶
"identity": {
"author": "John",
"author_email": "john@dummy.com",
"tags": ["computer"],
"dependencies": [],
"description": "A plugin description which may be on \n several lines",
"domogik_min_version": "0.4.0",
"name": "myplugin",
"type": "plugin",
"version": "0.1"
},
Some optional fields can be added in identity:
"identity": {
...
"xpl_clients_only": true,
...
},
"identity": {
...
"compliant_xpl_clients" : ["rfxcom-lan"],
...
},
Description¶
Mandatory :
- type : the package type. For a plugin, the value will always be plugin
- name : the plugin name. Example : ipx800, diskfree, ...
- version : the plugin version. It must respect the rules about version numbers
- domogik_min_version : the minimum Domogik compliant release. It can’t be lower than 0.4.0
- description : the plugin description
- author : nickname, full name, society or anything to identify the author of the plugin
- author_email : an email to contact the package author. If you want you can write the email like this : john at dummy dot com.
- tags : a list of tags. You will find some examples below.
- dependencies : a list of dependencies for the package. You will find more informations below.
Optionnal :
- xpl_clients_only : true|false. Default : false. This key is to set to true only for the plugins with no python part. These are special plugins that will be used only for adding compatibility to external xPL clients to Domogik. Example : the generic plugin.
- compliant_xpl_clients : an array of <vendor_id>-<device_id> identifiers. This array is to used when a plugin with a python part can also handle some external xpl clients. For example, the rfxcom plugin can handle both usb and lan models. The usb one needs python to work. The lan one just need the json. All xpl clients detected as corresponding to one of the <vendor_id>-<device_id> identifiers will be visually linked to the plugin in the Domogik administration interface.
Tags¶
Here is a list of suggested tags. If needed you can use new tags (if so, please send us a mail in order we may complete this list):
- appliance
- communication
- computer
- electricity
- energy
- gas
- hvac (heating, ventilation and air conditionning)
- heating
- light
- media
- online_service
- plc (power line carrier)
- relayboard
- security
- shutter
- telephony
- temperature
- water
- weather
- wireless
Dependencies¶
There are 3 types of dependencies:
- python packages or libraries
- Domogik plugins
- other dependencies
Both python and plugin can be checked by Domogik when installing a plugin, so it is very important to list the dependencies to avoid users to create new bug tickets just because they forgot to install a dependency.
Example for a python dependency:
"dependencies": [
{
"id": "pyserial (>=2.5)",
"type": "python"
}
],
Example for a plugin dependency:
"dependencies": [
{
"id": "cron",
"type": "plugin"
}
Example for an other dependency:
"dependencies": [
{
"id": "owfs (>=2.8.4)",
"type": "other"
}
Plugin json file : info.json¶
The info.json file is maybe the most important part of a plugin : it will describe the plugin, the configuration items and all interactions with xPL world. This file is divided in several sections:
{
"json_version": 2,
"identity": {...},
"configuration": [...],
"commands": {...},
"xpl_commands": {...},
"sensors": {...},
"xpl_stats": {...},
"device_types": {...},
}
The json_version must be set to 2 for Domogik 0.4 compliant plugins.
Warning
In json, you must write \n each time you want to create a newline in the data.
Plugins json file upgrade from version 1 to json version 2¶
Purpose¶
This document describes the step to follow for upgrading the plugin json file from version 1 to 2. With json version 2 the url2xpl and the stats xml files are deprecated, the content of these files are moved to the plugin json file (sensors and commands parts). This document only describes how to upgrade.
Todo
Finish:
- give more details
Note
Please notice that this page is mainly for information. To migrate your plugin json file from version 1 (Domogik 0.3) to version 2 (Domogik 0.4+) you should check also all the other pages related to the info.json file!
Configuration part¶
The following keys had been removed:
- id : just define the options in the appropriate order in the table
- interface (as now the way of handling multiple devices will change)
In the json file, the following keys are renamed:
- startup-plugin => auto_startup. And this key is also removed! You don’t need to add it in the json anymore, it is dynamically added by Domogik as all plugins needs it.
The following way of writing have changed:
- minus => underscore in keys
- yes/no => true/false
- empty => null
- optionnal => required
- the option part is optional if not required by the type.
The following items has been added:
- name : a human readable name for the configuration item.
Files part¶
There is no more files part as now the plugin files are contained in the same root directory. So when the package is created, all files are included.
Identity part¶
The following keys had been removed:
- no more changelog
The following way of writing have changed:
- category => tags
- id => name
Json sample file¶
Here is a sample of the json file. There are some comments in the sample. Please refer also to the dedicated documentation pages for all the json parts for more information.
Sample¶
Part of the json sample¶
# Here part of json declaration
"device_types": { # device type declaration who appears in user interface list to create device
"fooplugin.device_name": {
"id": "fooplugin.device_name", # device id for internal reference (no Xpl)
"description": "A short description",
"name": "The device type name",
"commands": ["cmd_one", "cmd_two"], # link to list of commands
"sensors": ["sensor_one", "sensor_two"], # link to list of sensors
"params": [ # parameters that are common to ALL xpl messages for this device type
{
"key": "key1_devicetype", # key copied to the xpl messages, this a static key like device address
"description": "A short description",
"type": "string" # key appears in the xpl as : {key1_devicetype : this a string type}
"xpl": True # if the params is in the xpl messages : True. For example, an interval will not be in a xpl message
"default": "A default value" # optionally a default value for this parameter
},
{
"key": "key2_devicetype",
"description": "A short description",
"type": "integer",
"max_value": 255, # depending on the type the are other possibilities
"min_value": 0,
"xpl": True # if the params is in the xpl messages : True. For example, an interval will not be in a xpl message
"default": "A default value" # optionally a default value for this parameter
}
]
}
},
"commands": { # commands who are call by device_types, A command can be call by many device_types
"cmd_one": {
"name": "The cmd name",
"return_confirmation": true, # at true a confirmation xpl message should be send at command return. It is strongly recommended to use confirmation
"parameters": [ # parameters that are common to ALL xpl messages for this command
{
"key": "key1_cmd_one", # key copied to the xpl messages, this a dynamic key that can be set in each Xpl message (dynamic)
# key appears in the xpl as : {key1_cmd_two : dynamic value}
"data_type": "DT_Switch", # link to Domogik Type of Data used by widgets in UI.
"conversion": "from_DT_Switch_to_level" # if needed define proper conversion function in file <plugindirectory>/conversion/<fooplugin>.py
# conversion have utility when hard device receive a particular scale or data type who are not directly compatible with Domogik Types
}
],
"xpl_command": "xpl_cmd_one" # link to xpl_command format to use
},
"cmd_two": {
"name": "The cmd name",
"return_confirmation": false,
"parameters": [
{
"key": "key1_cmd_two",
"data_type": "DT_Scale",
"conversion": "from_DT_Scale_to_level"
}
],
"xpl_command": "xpl_cmd_two"
}
},
sensors": { # sensors who are used by device_types, A sensors can be use by many device_types
"sensor_one": {
"name": "The sensor name",
"data_type": "DT_Switch",
"conversion": "from_level_to_DT_Switch", # if needed define proper conversion fuction in file <plugindirectory>/conversion/<fooplugin>.py
# conversion have utility when hard device send a particular scale or data type who are not directly compatible with Domogik Types
"incremental" : false, # does the sensor is about incremental values
"timeout" : 120, # value in seconds to indicate when a value is too old and so the device may be offline/broken/...
"history": { # how is managed the sensor history.
"store": true,
"duplicate": false,
"max": 0,
"expire": 0,
"round_value": 0
}
},
"sensor_two": {
"name": "The sensor name",
"data_type": "DT_Scaling",
"conversion": "from_level_to_DT_Scaling",
"incremental" : false,
"timeout" : 0,
"history": {
"store": true,
"duplicate": false,
"max": 0,
"expire": 0,
"round_value": 0
}
},
"xpl_commands": { # declaration for linking commands to xpl schema and keys
"xpl_cmd_one": {
"name": "The xplCmd name",
"schema": "fooplugin.basic", # schema that must respect the xpl-protocol
"xplstat_name": "xplstat_name_one",
"parameters": { # parameters that are common to ALL xpl messages for this xpl_command
"static": [ # key copied to the xPL message. Static keys will always be the same, whatever the device is
{
"key": "static_key1_xpl_cmd_one", # key appears in the xpl as : {static_key1_xpl_cmd_one : static value}
"value" : "value_static_key1_xpl_cmd_one"
},
{
"key": "static_key2_xpl_cmd_one",
"value" : "value_static_key2_xpl_cmd_one"
}
],
"device": # key copied to the xPL message. Satic value defined on a per device basis (input is requested) in the user interface.
# The device key is used for parameters that depend on a device, that require an input by the user,
# but that do not change once the device is created, once the device is cerated these parameters
# are static, but the value is requested during the device creation process.
# an example of a device parameter is the datatype in knx, this can be different for xplstat and xplcommand,
# but it will stay the same once the device is created
{
"key": "device_key1_xpl_cmd_one", # key appears in the xpl as : {device_key1_xpl_cmd_one}
"description": "A description", # A description that will be displayed in the admin interface
"type": "string" # The type of the field (used for the userinterface)
"default": "A default value" # optionally a default value for this parameter
},
{
"key": "device_key2_xpl_cmd_one", # key appears in the xpl as : {device_key1_xpl_cmd_one}
"description": "A description", # A description that will be displayed in the admin interface
"type": "string" # The type of the field (used for the userinterface)
"default": "A default value" # optionally a default value for this parameter
}
]
}
},
"xpl_cmd_two": {
"name": "The xplCmd name",
"schema": "fooplugin.basic",
"xplstat_name": "",
"parameters": {
"static": [
{
"key": "static_key1_xpl_cmd_two",
"value" : "value_static_key1_xpl_cmd_two"
}
],
"device": [
{
"key": "device_key1_xpl_cmd_two", # key appears in the xpl as : {device_key1_xpl_cmd_one : static value}
"description": "A description", # A description that will be displayed in the admin interface
"type": "string" # The type of the field (used for the userinterface)
"default": "A default value" # optionally a default value for this parameter
}
]
}
}
},
"xpl_stats": { # declaration for linking stats to xpl schema and keys.
"xplstat_name_one": {
"name": "The xplstat name",
"schema": "sensor.basic", # schema that must respect the xpl-protocol
"parameters": { # parameters that are common to ALL xpl messages for this xpl_stats
"static": [ # key copied to the xPL message. Static keys will always be the same, whatever the device is
{
"key": "static_key1_xplstat_one", # key appears in the xpl as : {static_key1_xplstat_one : static value}
"value" : "value_static_key1_xplstat_one"
}],
"device": [
{
"key": "device_key1_xpl_cmd_two", # key appears in the xpl as : {device_key1_xpl_cmd_one}
"description": "A description", # A description that will be displayed in the admin interface
"type": "string" # The type of the field (used for the userinterface)
"default": "A default value" # optionally a default value for this parameter
}
],
"dynamic": [ # key copied to the xPL message. keys which are values for the sensors. They are stored in the sensor history table.
{
"key": "dynamic_key1_xpl_xplstat_one", # key appears in the xpl as : {dynamic_key1_xpl_xplstat_one : dynamic value}
"ignore_values": "",
"sensor": "sensor_one" # link to sensor type to set Domogik Data Type and optimal conversion
},
{
"key": "dynamic_key2_xpl_xplstat_one",
"ignore_values": "",
"sensor": "sensor_two"
}
]
}
}
}
An xpl command for cmd_one with confirmation¶
Command:
xpl-cmnd
{
hop1 = 1
source = xpl-rest.domogik
target = *
}
fooplugin.basic
{
key1_devicetype_one = static-value_string # ex : {"deviceaddr" : "x0103"}
key2_devicetype_one = static-value-integer # ex : {"channel" : "a1"}
key1_cmd_one = dynamic-value-DT_Switch # ex : {"command" : "on"}
static_key1_xpl_cmd_one = value_static_key1_xpl_cmd_one # ex : {"zone" : "bedroom"}
static_key2_xpl_cmd_one = value_static_key2_xpl_cmd_one # ex : {"timer" : 15}
device_key1_xpl_cmd_one = value_device_key1_xpl_cmd_one # ex : {"bauds" : 40000}
device_key2_xpl_cmd_one = value_device_key2_xpl_cmd_one # ex : {"sleep" : 2}
}
The confirmation message:
xpl-trig
{
hop = 1
source = domogik-fooplugin.foomachine
target = *
}
sensor.basic
{
key1_devicetype = static-value_string # ex : {"deviceaddr" : "x0103"}
key2_devicetype = static-value-integer # ex : {"channel" : "a1"}
static_key1_xplstat_one = value_static_key1_xplstat_one # ex : {"zone" : "bedroom"}}
device_key1_xpl_xplstat_one = value_device_key1_xpl_xplstat_one # ex : {"bauds": 40000
dynamic_key1_xpl_xplstat_one = dynamic-value-sensor-one-DT_Switch # ex : {"state" : "on"}
dynamic_key2_xpl_xplstat_one = dynamic-value-sensor-two-DT_Scaling # ex : {"battery" : 80}
}
An xpl command for cmd_two without confirmation¶
Note
This case should not happen! xPL protocol tell us that a trigger (xpl-trig) message should be sent after each successfully processed command (xpl-cmnd) received!
Command:
xpl-cmnd
{
hop1 = 1
source = xpl-rest.domogik
target = *
}
fooplugin.basic
{
key1_devicetype_one = static-value_string
key2_devicetype_one = static-value-integer
key1_cmd_two = dynamic-value-DT_Scale
static_key1_xpl_cmd_two = value_static_key1_xpl_cmd_two
device_key1_xpl_cmd_two = value_device_key1_xpl_cmd_two
}
There is no confirmation message
info.json for plugins - sensors section¶
Purpose¶
The sensors section will quickly describe each sensor with:
- a name
- a data type. See the data types documentation for more informations
- if needed, some conversion options
Example 1¶
This example is the diskfree plugin. See the device types documentation for more informations.
For each sensor, we define a name and no conversion option. Then, depending of the sensor we use 2 data types:
- DT_Byte : this one is used for values in byte, which are the values returned by the sensors get_free_space, get_used_space and get_total_space.
- DT_Scaling is used for values in percent from 0 to 100, which corresponds to the value returned by the sensor get_percent_used.
"sensors": {
"get_total_pace": {
"name": "Total Space",
"data_type": "DT_Byte",
"conversion": "",
"incremental": false,
"timeout" : 0,
"history": {
"store": true,
"duplicate": false,
"max": 0,
"expire": 0,
"round_value": 0
}
},
...
}
Description¶
Each item has several properties:
name : it is a string used to display this sensor on the UI
data_type : the data type used for this sensor
incremental : if set to True, store the difference between the last value and the current value, check the incremental section for more info.
timeout : this is just an information which could be used by user interfaces to notify if a value is too old (this may indicate that the sensor is down/offline/broken. This is a value in seconds. If the value is 0, then we consider there is no issue if the last value is very old.
conversion : if not an empty string, what conversion function to call before storing the value in the db, the function should return the data in the format according to the data_type. More informations in the conversion chapter.
- history : some extra parameters that can be used to define what to store in the history table
- store : can be True or False, if True the values will be stored in the sensor_history table
- max : max number of records that will be stored in the history table, if 0 the max number is infinite.
- expire : how long the history needs to be kept, if 0 the stats will be kept forever. Its counted in days, so a value of 10 means keep the history for 10 days.
- round_value : a number that will be used for the reduced stats storage. This will only be evaluated if store is True
- duplicate : if set to true, duplicate values following each other will be stored, if set to false this will not happen
Note
The values in the subsection history can be adapted after sensor creation, the changes will be visible in the sensor_history table once a new value is stored for that sensor.
Note
If a sensor is set as incremental its not a good idea to set round_value to anything different then 0. This could result in corrupt data in the sensor_history table.
What happens if a sensor value needs to be store¶
A couple of steps are taken during sensor lookups, they are described below:
- First check if we can match the xpl source to a known plugin
- Search for a matching xpl_stat message thats stored in the db (can be multiple)
- Record all sensors + the value to store per xpl_stat
- For every sensor/value pair check if the value is in the ignore_values, go to step 11
- For every sensor/value pair run the conversion function if defined
- For every sensor/value pair handle the incremental field if needed, value = <received value> - <last stored value>
- For every sensor/value pair handle the duplicate field if needed
- For every sensor/value pair handle the formula if needed
- For every sensor/value pair handle the round value
- for every sensor/value store the value if sensor.store is set
- Send an mq pub message device-stats with the device id, sensor id and the value
Incremental explanation¶
The parameter is used for sensors like a kWH sensor, these sensors typically send over an absolute value, if we would store this value we would just get a climing chart, so the charts would not be representative. To solve this we introduced the incremental sensor type.
This means that only the difference between the last stored value will be stored, to explain it exactly w’ll work with an example.
For a sensor that has incremental set tue, the following values are received: 1. 10 2. 11 3. 12 4. 15 5. 16 6. 18
So on the receival of the first value, incremental will just keep this value in memory, so it can calculate the difference on the next received values.
On step 2 we will store the difference between the value received on step 1 and the one received in step 2, so we will store 1. The same happens on all the next steps. As a result we will get the following stored values: 1. 0 2. 1 3. 1 4. 3 5. 1 6. 2
This will result in a chart that will really display the used kWH during the time period between the 2 steps.
Round Value explanation¶
The round value is used to reduce the number of stored history values.
As an example why this would be needed: Some device can collect data every 10 seconds, in 24 hours, the device can collect 8640 items a day. If we have 10 such a devices it would result in 86400 items a day or 2678400 items a week. This would be to much data and is not useful. To solve this problem we introduced the round_value key. Basically the round_value key will delete values that fall within a predefined range. An example explains this the best:
Below is a list of received values from a sensor: 1. 10 2. 11 3. 12 4. 9 5. 15 6. 16 7. 18 8. 19 9. 20
So lets see what will happen if round_value is set to 2:
On step 1 and 2 the round value will do nothing as it needs at least 2 stored values to work. On step 3 the round value will do its first action, it will see that the difference between value from step 1 is smaller or equal to round_value key, meaning that it will delete the value received in step 2. On step 4 it will kick in action again, at this point the data in the table is the following * 10 * 12 The received value is 9, as the round_value is set to 2, it will not delete anything. The same happend is step 5 and 6. On step 7 the difference is again <= round_value, so the value received in step 6 is deleted. So if we see what data we will have after value 9 is received:
- 10
- 12
- 9
- 15
- 18
- 20
In this example this will save use 66% of storage space, so for the example in the beginning of this section we will go from 2678400 items a week to 1767744 items a week.
For this system to work its very important that the round_value is set to a logical number for that type of sensor.
Formula Field¶
This is a field that is NOT in the json, but its a sensor parameter. This means it can be updated after sensor creation. With the formula field you can apply a formula to a value that will be stored. The formula calculation is one of the last steps in the sensor history storage.
The formula needs an input parameter and thats the value coming from XPL. To use this value in you formula use VALUE string, it will be replaced before the formula is applied.
example: VALUE - 5 This will store the received XPL value - 5
If the formula is not a valid formula (means it can not be evaluated) the exception is logged in the db_api.log and the original value will be stored in the DB
info.json for plugins - xpl_commands section¶
Purpose¶
The xpl_command section will describe all xPL messages that must be sent by Domogik to trigger an action in the plugin.
Example¶
This example is the velbus plugin. See the device types documentation for more informations.
"xpl_commands": {
"set_level_bin": {
"name": "blah",
"schema": "lighting.basic",
"xplstat_name": "get_level_bin",
"parameters": {
"static": [],
"device": []
}
},
...
Description¶
For each message you need to set:
name: it will be displayed in the user interface when you will configure a widget.
schema: the xPL schema used by the plugin.
xplstat_name: The xPL stat/trig message that should be received ones the command is executed by the plugin
parameters: all the parameters of the xPL message.
static: all the static keys of the xPL message. Static keys will always be the same, whatever the device is. They will never be displayed in the user interface.
- key: The xpl key
- value: The value the xpl key must have
device : the static parameters. Their value is defined on a per device basis (input is requested) in the user interface.
- description : short description of this field
- type : the value type for this field. The available values are the same as the ones used in the configuration part of the json
- key : The Xpl key
- default : Optionaly a default value for this parameter, will be pre filled in in the admin interface during device creation
info.json for plugins - xpl_stats section¶
Purpose¶
The xpl_stats section will describe all xPL messages that may be received by some sensors. No difference is made between xpl-stat and xpl-trig messages : as some xpl client implement these messages not as required in the xPL specification, we process them in the same (this is not an issue as basically xpl-trig and xpl-stat contains the same data : xpl-trig is just used for events and data changes and xpl-stat for all other usages).
Example 1¶
This example is the diskfree plugin. See the device types documentation for more informations.
"xpl_stats": {
"get_percent_used": {
"name": "Percent used",
"schema": "sensor.basic",
"parameters": {
"static": [
{
"key": "type",
"value": "percent_used"
}
],
"device": [],
"dynamic": [
{
"key": "current",
"ignore_values": "",
"sensor": "get_percent_used"
}
]
}
},
"get_used_space": {
...
},
"get_total_space": {
...
},
"get_free_space": {
...
}
},
Description¶
Here there is 4 sorts of xpl messages that may be received. Each one has its proper subsection here:
- “get_percent_used”: {...}
- “get_used_space”: {...}
- “get_total_space”: {...}
- “get_free_space”: {...}
The key used is free and you can put anything you want. In this plugin, each sensor has its own xpl message, so we choose to use for this key the name of the related sensor. This key will be inserted in database only for reference (it is not used, it is just in the database to help for debugging to find the related data in the json file).
For each message you need to set:
- name: it will be displayed in the user interface when you will configure a widget.
- schema: the xPL schema used by the plugin.
- parameters: all the parameters of the xPL message.
- static: all the static keys of the xPL message. Static keys will always be the same, whatever the device is. They will never be displayed in the user interface.
- key: The xpl key
- value: The value the xpl key must have
- device : the static parameters. Their value is defined on a per device basis (input is requested) in the user interface. The values can be different for the xpl_commands and xpl_stats* parts (if both exists).
- description : short description of this field
- type : the value type for this field. The available values are the same as the ones used in the configuration part of the json
- key : The Xpl key
- default : Optionally a default value for this parameter, will be pre filled in in the admin interface during device creation
- multiple : Optional parameter, if this parameter is set to a one char string, then we can set multiple values for the parameter separated by the char. Example input: <value1><separator><value2><separator><value3>
- dynamic : all the dynamic parts of the xPL message. It is all the keys which are not related to the device address : values for the sensors. They are stored in the sensor history table.
- key : The Xpl key
- ignore_values : What values to ignore, see below for more info
- sensor : The sensor to store this value in
- static: all the static keys of the xPL message. Static keys will always be the same, whatever the device is. They will never be displayed in the user interface.
ignore_values¶
This parameter for a dynamic xpl_stat parameter is used to ignore certain values. If the received value (in the xpl_stat message) is found in this list then the value will not be stored in the sensor_history table, if we don’t find it in the list or if the list is empty the value will be stored in the sensorHistory table.
Create main folders and files in one shot¶
There is an online repository which can help you to create a plugin template : https://github.com/vdomos/domogik_create_plugin_sample
The plugin file tree¶
First, as already said in the chapter about creating the plugin repository on GitHub, there are some rules about a plugin name : don’t forget to respect them.
The plugin file tree will at least contains (assuming the plugin name is myplugin):
- start.sh : a script to start the plugin manually
- __init__.py : an empty file needed by python
- info.json : the json file which describe the plugin and its features. This file must always be named like this!
- bin/ : the main python program will be in this folder
- bin/__init__.py : an empty file needed by python
- bin/myplugin.py : the python main part. This file must be always named like this : bin/myplugin.py. Example for the diskfree plugin : bin/diskfree.py.
- lib/ : the python libraries
- lib/__init__.py : an empty file needed by python
- lib/myplugin.py : the python library part
- conversion : the python conversion functions
- conversion/__init__.py : an empty file needed by python
- conversion/from_foo_to_bar.py : a python conversion function
- conversion/from_fii_to_bur.py : another python conversion function
- doc/ : the sphynx documentation in ReST format
- design/ : this folder will contain all graphical resources (icons, ...)
- design/icon.png : the plugin icon (png, 96px * 96px). This file must always be named like this!
- udev/ : this folder will contain the udev rules needed for this plugin
Some other items may be added:
- data : if needed this folder can contain data needed by the plugin or the plugin may write data in it
- tests : if the plugin have some tests scripts, they must be here
Plugin binary part¶
TODO : “helpers” for special pages
Purpose¶
First, as already said in the chapter about creating the plugin repository on GitHub, there are some rules about a plugin name : don’t forget to respect them.
The binary part of a plugin will in all cases be a single file. Assuming your plugin name is myplugin, the file name will be bin/myplugin.py.
This file is the gateway between the library and Domogik. Basically it will:
- import the needed libraries
- check if the plugin is configured
- get the plugin configuration values
- instantiate the library class
- use the library and thanks to callback functions communicate wih Domogik
- set the plugin as ready when it is fully started
Depending on your plugin, more actions may be done:
- after getting the configuration, get the devices list from the database (over the MQ). This is needed when the plugin can’t know the devices addresses, for example a weather plugin don’t know in which town you live, so it will check the weather for already created devices.
- register a newly detected device. For example, if a new sensor is plugged on your onewire network or if you add a new temperature sensor compliant with RFXCOM devices, the related plugins (onewire and rfxcom) will be able to detect the new devices, and if the device is not known, it will be registered, so a notification will be send to the user interfaces over MQ.
XplPlugin and its useful functions¶
- ..todo ::
- overview
Helper function¶
- register_helper(action, help_string, callback)
- publish_helper(key, data)
On demande features¶
- ..todo ::
- ready, config, library, packages, resources, data getter functions
- logging, force_leave (and return code),
- MQ usage
Template of the binary part¶
Header¶
First, you need to put a header in the file. Here is a template:
#!/usr/bin/python
# -*- coding: utf-8 -*-
""" This file is part of B{Domogik} project (U{http://www.domogik.org}).
License
=======
B{Domogik} is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
B{Domogik} is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Domogik. If not, see U{http://www.gnu.org/licenses}.
Plugin purpose
==============
A quick description of the plugin
Implements
==========
MyClassName()
- __init__()
- a_function()
@author: The developer name <the.developer@email.com>
@copyright: (C) 2007-2013 Domogik project
@license: GPL(v3)
@organization: Domogik
"""
The first line is called the shebang. The second line specify to python that we will use the utf-8 coding. Then, there is a big comment part : “””...”“” It contains 4 sections:
- License: this should be the same for all Domogik plugins. It says that the file/plugin is uses the GPL license.
- Plugin purpose: a quick description of the plugin.
- Implements: this should list of the implement classes and methods
- the 4 lines which starts with a @: fill the first line with your name or surname and your email.
Import the needed libraries¶
Then, you need to import some libraries needed by the plugin. Example:
from domogik.xpl.common.plugin import XplPlugin
from domogik.xpl.common.xplmessage import XplMessage
from domogik_packages.plugin_diskfree.lib.diskfree import Disk
import threading
import traceback
Some import lines are mandatory:
from domogik.xpl.common.plugin import XplPlugin # this one is needed as all the xPL plugins extends from the class XplPlugin. This class will provide you some useful functions to get the configuration parameters values, ...
from domogik.xpl.common.xplmessage import XplMessage # this one is needed to create a xPL message.
Then, you have to import your plugin library objects (here for the plugin diskfree we import a class):
from domogik_packages.plugin_diskfree.lib.diskfree import Disk
To finish, depending on the plugin needs, you may need to import some various librairies. Example:
import threading
import traceback
Create the main class¶
Now, you can create your main class. Here is an empty class template and the final part of the plugin bin file which will allow to instantiate this class when you execute the bin file:
class MypluginManager(XplPlugin):
""" A description about the class
"""
def __init__(self):
""" The constructor of your class. This function will be called when the class is instantiated.
"""
XplPlugin.__init__(self, name='myplugin')
# do some other actions
if __name__ == "__main__":
MypluginManager()
Of course, rename MypluginManager and myplugin with your plugin name!
What should be done in the main class ?¶
Well, it depends on your plugin!
First, you should handle the configuration part:
- get the global configuration elements for your plugin (see the configuration part of the json file)
- eventually, get the created devices list and for each device get its configuration values
- if needed, do some checks about the configuration elements and the devices
Then, start the features:
- if the plugin manage some sensors, launch some threads to listen for the sensors (the threads should launch functions from the plugin library)
- if the plugin manage some actuators, create some listeners for xPL commands messages. These listeners will call some callback functions (which should be defined in the library part)
Finally, tell Domogik that the plugin is ready:
- when all is ready, call the ready() function
Focus on the start process¶
The plugin global configuration¶
If you except the mandatory auto_startup key, a plugin can have no configuration elements and another one can have multiple configuration elements. A plugin without configuration elements can be started without any configuration from the user (but maybe the user will need to create some devices, we will see this later). But for plugins with some configuration elements, the plugin developer may want to check that the plugin has been configured before the plugin can start! This can be done by calling the function ``self.check_configured()`` and check its return value:
# check if the plugin is configured. If not, this will stop the plugin and log an error
if not self.check_configured():
return
How this functions works ? It is quite simple: when the user save the plugin configuration on a user interface, a configured key is inserted in database for the plugin and the host on which it is installed. If this key is not set to true in database, when you call this function, it will return False.
To retrieve a configuration parameter value, you just need to do this:
self.interval = self.get_config("interval")
This function will check in database if a value is set for the key interval and the host where the plugin is installed. If there is such a value, it will be cast returned and casted to the data type configured in the json file. If there is no value, the default value configured in the json file will be returned. So, you don’t need to cast the value and you don’t need to handle some default value as it is already done in the json file.
Notice that the auto_startup configuration key which is dynamically added to the configuration parameters set in the info.json file is not to handle in the plugin. This is a configuration key used only by the manager.
Get the created devices list for a plugin¶
Some plugins may need to know the devices on which they need to interact. To get the devices list, just do:
# get the devices list
self.devices = self.get_device_list(quit_if_no_device = True)
If your plugin needs some devices to be created to be run, set the parameter quit_if_no_device to True. If you do so and if no device exists when the plugin starts, the plugin will stop itself and log an error about this.
Then, you can make a look on the device list and for each device get its parameters and do something (launch a thread, create a listener, ...). Example for the loop:
# loop on all found devices
for a_device in self.devices:
try:
# get the configuration values for the key 'the_key' for the sensor 'the_sensor' of the device
the_value = self.get_parameter_for_feature(a_device, "xpl_stats", "the_sensor", "the_key")
# do something
except:
# if there is an error, log it
self.log.error(traceback.format_exc())
# if the error is something blocking (a hardware gateway unavailable for example), you may want the plugin to stop.
# if so, uncomment the following lines
#self.force_leave()
#return
Here is an example from the diskfree plugin:
# get the devices list
self.devices = self.get_device_list(quit_if_no_device = True)
# instantiate the class of the library
# notice that we sent as parameters some callbacks :
# * the logger object (self.log)
# * the function to send a xPL messages
# * a function to help to stop the plugin
disk_manager = Disk(self.log, self.send_xpl, self.get_stop())
# loop on all found devices
threads = {}
for a_device in self.devices:
try:
### feature get_total_space
# get the path to check for the sensor 'get_total_space' of the device. The configuration key is 'device'
path = self.get_parameter_for_feature(a_device, "xpl_stats", "get_total_space", "device")
# get the interval between each check for the sensor 'get_total_space' of the device. The configuration key is 'interval'
interval = self.get_parameter_for_feature(a_device, "xpl_stats", "get_total_space", "interval")
self.log.info("Start monitoring total space for '%s'" % path)
# start a thread
# the thread must be named to be explicit in the logs
thr_name = "{0}-{1}".format(a_device['name'], "get_total_space")
# create the thread
threads[thr_name] = threading.Thread(None,
disk_manager.get_total_space,
thr_name,
(path, interval,),
{})
# start the thread
threads[thr_name].start()
self.register_thread(threads[thr_name])
# [...] 3 other features are managed like this in the plugin
except:
# if there is an error, log it
self.log.error(traceback.format_exc())
# we don't quit plugin if an error occurred
# a disk can have been unmounted for a while
#self.force_leave()
#return
Notice in this example that we get the devices list, then instantiate the class of the library and finally, for each device, call a method (or more if needed) from the library class in a thread.
Perform updating devices process¶
After declaring an instance of an handling update method of devices, you can register it as callback
# register a callback device.update MQ message.
self.register_cb_update_devices(myHandleDeviceUpdate)
Your myHandleDeviceUpdate method will be called each device.update event comming from MQ pub message. That method must have devices list as parameters. Example of refreshing method to register
# A methode to handle updated devices by callback, in Class declaration :
def myHandleDeviceUpdate(self, devices):
for hardDevice in self._myHardDevices:
hardDevice.refreshAllDmgDevice(devices)
self.log.info(u"All hard devives are updated from domogik devices")
Updating global parameters of device
# @param paramId: db id of global parameters getting from dict device['parameters']['myGlobalParam']['id']
# @param value : New parameter value.
# @return : True if success else False.
self.udpate_device_param(param_id, value)
Note
Overwrite Plugin class method on_message needed to call the parent method at first
# Your new overwrite methode
def on_message(self, msgid, content):
Plugin.on_message(self, msgid, content)
....
Focus on the end : plugin ready¶
At the end of your __init__ function, just add these 2 lines:
self.ready()
self.log.info("Plugin ready :)")
Focus on devices tools¶
Get a data types specifications¶
If you need a data type detail definition to handle a sensor/command. To get it use Plugin class method
@param name : the name of DT_Type
@return : dict DT_Type himself, empty dict if not find.
myDataType = self.get_data_type(name)
Get json product information¶
Get product informations from json plugin can helpfull for detection devices. To get it use Plugin class method
# @param productId : part of productId set in json plugin. Search it in lower case if your <myProductId> is contained in product['id'] string.
# @return : dict of product defined in json plugin with picture file name added. If not find empty dict.
# {
# "name" : The name of product,
# "id" : product id, this is base name of picture,
# "documentation" : Link to manufacturer documentation, manual, specification,
# "type": Device_type linked to this product,
# "picture" : if exist, file name of picture representing product without full path, else None
# }
self.get_product_by_id(self, myProductId):
Get json sensor definition¶
Get sensor defined in json plugin, helpfull for detection devices
Search by Id
# @param id : sensor id to find set in json plugin. # @return : dict of sensor if find else empty dict. sensor = self.get_sensor_by_id(mySensorId):
Search by name
# @param name : value of key name set in json plugin, Search in lower case. # sensors" : { # "my_sensor" : { # "name" : "mySensorName", <== key compared to param <name> # ... # @return : dict of all sensors with same key name. If not find return empty dict. # { # "my_sensor1" : { # "name" : "mySensorName", # ... # }, # "my_sensor2" : { # "name" : "mySensorName", # ... # }, # ... # } sensor = self.get_sensors_by_name(mySensorName)
You could have several returned sensors if sensors json plugin have same name
Get json command definition¶
Get command defined in json plugin, helpfull for detection devices - Search by Id
# @param id : command id to find set in json plugin.
# @return : dict of commands if find else empty dict.
command = self.get_command_by_id(myCommandId):
Search by key value
# @param key : value of one key parameter set in json plugin. Search in lower case. # commands" : { # "my_command" : { # "name" : "foo name", # "parameters" : [{ # "key" : "myKeyValue", <== value of key compared to param <key> # ... # }] # @return : dict of all command include same key. If not find return empty dict. # { # "my_command1" : { # "name" : "foo name2", # "parameters" : [{ # "key" : "myKeyValue", # ... # }] # }, # "my_command2" : { # "name" : "foo name2", # "parameters" : [{ # "key" : "myKeyValue", # ... # }] # }, # ... # } commands = self.get_commands_by_key(myKeyValue)
You could have several returned commands if commands json plugin have same key value
Focus on the logs¶
There are 4 methods that enable you to log informations in the log files. The default logfiles are located in the directory configured in /etc/domogik/domogik.cfg as log_dir_path (default is /var/log/domogik/). The filename is myplugin.log.
Here are the 4 methods:
self.log.error("a message")
: log an error messageself.log.warning("a message")
: log a warning messageself.log.info("a message")
: log an information messageself.log.debug("a message")
: log a debug message
Error¶
The error messages are to be used to log all critical or blocking points. You must use this :
- each time the plugin can’t open a resource (hardware, file, url)
- each time the plugin catch some important exception, as a connexion lost to a hardware, url, ...
- if something is wrong in the plugin configuration
- ...
Warning¶
The warning messages are to be used to log the small errors (with no real impact : for example an incorrect value is read from a sensor but it may happen as the technology is not 100% reliable) or some strange behaviour that are not blocking points
Info¶
The info messages must be used for important informations:
- some informations about the plugin startup
- any information that may help someone who reads the log to check the plugin started successfully
- give some informations about the hardware (if the plugin uses some hardware) : firmware version, configuration modes, ...
- ...
You must not use this log level for debug messages, for example:
- avoid to use this level in eternal loops (to avoir big log files). Use the debug level in this case
Debug¶
The debug log level is not activated with Domogik official packages, so these messages won’t be written in the log files. The development version of Domogik has this log level activated. The debug messages are to be used for: * logging any step in the plugin processing * log actions in the eternal loops * log important variable status * log any connection to a service or a hardware. For example, with a serial device you must log all that is sent to the device or all that is received from the device. These are very important informations for debugging! These informations can also be used later to create some automated tests or fix bugs and test them thanks to the user logs. * ...
You can and you must use the debug messages! They are evry important when a bug is discovered as they allow anybody to check what is the issue.
Using the logs in the library part¶
Focus on xPL : send xPL messages and listen for xPL messages¶
Send xPL messages¶
To allow the plugin to send some xPL messages, you must create such a function in the binary part. The function will not be the same for all the plugins and depends on the plugin features and on the xPL schema used.
Here is a template:
def send_xpl(self, arg1, arg2, ...):
""" Send xPL message on network
"""
self.log.debug("Sending xPL message for arg1={0}, arg2={1}...".format(arg1, arg2))
msg = XplMessage()
msg.set_type("xpl-stat")
msg.set_schema("aschema.basic")
msg.add_data({"key1" : arg1})
msg.add_data({"key2" : arg2})
# ...
self.myxpl.send(msg)
This function should be called from the library part. To allow the library part to call this function, you must sends this function as a callback parameter to the library.
Here is an example of the function for the sensor.basic xPL message:
def send_xpl(self, address, type, value):
""" Send xPL message on network
"""
self.log.debug("Values for {0} on {1} : {2}".format(type, address, value))
msg = XplMessage()
msg.set_type("xpl-stat")
msg.set_schema("sensor.basic")
msg.add_data({"device" : address})
msg.add_data({"type" : type})
msg.add_data({"current" : value})
self.myxpl.send(msg)
Let’s see how to use it with the library...
First, when you instantiate the class of the library, set the function as a parameter (this is call a callback):
my_object = MyLibraryClass(self.log, self.send_xpl, self.get_stop())
As you can see here, the self.send_xpl
function is set as a parameter. We will see in the library documentation how to use it.
Listen for xPL messages¶
Todo
TODO !
Focus on launching threads¶
When you want to launch an infinite task, you can use a thread. In this thread you can have an infinite loop with a timer in it or an infinite reading on something.
To be sure that the thread could be stopped when the plugin is requested to stop, you will need to use a stop flag in the library. So you need to give this stop flag to the library, for example when instantiating the library class. Example:
my_object = MyLibraryClass(self.log, self.send_xpl, self.get_stop())
The function self.get_stop()
will get the stop flag of the plugin and so give it to my_object. Then, in the methods of the library class, you will be able to handle this flag in the loops. You can read the library documentation to see how to handle it.
Then, you can create your thread. Example:
import threading
# [...]
class MypluginManager(XplPlugin):
# [...]
def a_function(self):
# [...]
# instantiate the class of the library
my_object = MyLibraryClass(self.log, self.send_xpl, self.get_stop())
# set the thread name
thr_name = "a_name_for_my_thread"
# create the thread
my_thread = threading.Thread(None,
my_object.the_function,
thr_name,
(arg1, arg2,),
{})
# start the thread
my_thread.start()
self.register_thread(my_thread)
Notice that a function self.register_thread() has been called. This function is important is it register the thread in a list. When the plugin is requested to stop, this list is used to help killing the existing threads! If your plugin doesn’t stop, please check that all your threads are registered.
Focus on the json file¶
If you need, for some reasons to access the json file content, please notice that:
- you should really ask yourself if the data you are looking for is not available somewhere else (over MQ or over REST).
- the json content is available in self.json_data. Don’t write in it!!!
Devices detection¶
Purpose¶
Some plugins can see all devices for the related hardware or service. For example, rfxcom plugin can catch all messages of all devices seen by the Rfxcom product. The onewire can also see all devices when reading the onewire bus. When a device which is not known from Domogik is detected, a message is sent over the MQ and catched by the user interfaces for being displayed.
How to implement this ?¶
Allow 0 devices created by the user¶
First, you should make your plugin able to start without any devices created by the user. So, the first time, the user will start the plugin and then will be able to create some devices from the user interfaces.
Note
Keep in mind that for now, when a new device is created, the user has to restart the plugin!
On plugin startup¶
First, in the __init__() function of your bin class, get the devices list like this (don’t forget the comment to explain why you don’t quit the plugin on startup if no devices are created):
# get the devices list
# for this plugin, if no devices are created we won't be able to use devices.
# but.... if we stop the plugin right now, we won't be able to detect existing device and send events about them
# so we don't stop the plugin if no devices are created
self.devices = self.get_device_list(quit_if_no_device = False)
Then, when you instantiate your plugin lib class, add the self.device_detected() function as a parameter (this is named a callback). Example for the rfxcom plugin:
self.rfxcom_manager = Rfxcom(..., self.device_detected, ...)
In the library¶
First, get the callback for the device_detected() function like this (this is still the rfxcom example):
class Rfxcom:
""" Rfxcom
"""
def __init__(self, ..., cb_device_detected, ...):
""" Some blah blah
@param ...
@param cb_device_detected : callback to handle detected devices
@param ...
"""
...
self.cb_device_detected = cb_device_detected
...
Then, in the plugin lib class, the way to detect a device can be different from a hardware/service and another one. When you detect a device, don’t focus if it is a known device or not and just get some informations about it:
- the items about its address or parameters
- its model or reference if possible
- any other information that may be useful during a device creation
Then, for each of its feature, call the callback for the device_detected() function. Here is an example for the rfxcom plugin:
# handle device features detection
for feature in ['temperature', 'humidity']:
self.cb_device_detected(device_type = "rfxcom.temperature_humidity",
type = "xpl_stats",
feature = feature,
data = {"device" : address,
"reference" : model})
Note
Be careful about the reference keyword. This is the only one which must always be named like this! The other ones may be related to the device_types section of the info.json file. Example for rfxcom (we find the same device key which is for this feature of this plugin the device address):
"device_types": {
"rfxcom.temperature": {
"description": "",
"id": "rfxcom.temperature",
"name": "Temperature sensors",
"commands": [],
"sensors": ["temperature", "battery", "rssi"],
"parameters": [
{
"key": "device",
"xpl" : true,
"description": "Device address. Example: th9 0xFFFF",
"type": "string"
}
]
},
There is nothing else to do! This function will check if the device is already created in database. If the device is not created, this device will be stored in memory and a MQ message device.new will be sent.
Python __init__.py files¶
Several __init__.py file must be created. They are needed in each folder which contains some python files (directly in the folder or in any subfolders). To create them, just do from the package root directory:
$ touch __init__.py
$ touch bin/__init__.py
$ touch lib/__init__.py
$ touch conversions/__init__.py
Plugin library part¶
TODO : “helpers” for special pages
Purpose¶
The library part of a plugin can be the first step of your plugin. It you write it in the good way, this library may be usable out of Domogik!
In most cases, a library will contain a class. This class will have a constructor, some functions for actuators features (these functions will be called from the binary part when some xPL messages are catched), some functions for the sensor part (in most cases, it will be an eternal loop to listen to the hardware or to call some services). The sensor functions will be able to send xPL messages by calling some callback functions which refers to functions in the binary part.
Template¶
- ..todo ::
- inifinite loop
- timer
Focus on the logs¶
You already learned how to use the log functions in the library part documentation. Let’s see how to do this in the library part.
- ..todo ::
- TODO :)
Focus on xPL : send xPL messages and listen for xPL messages¶
Send xPL messages¶
As seen in the library part documentation, the function to send xPL messages are sent to the library class constructor as a parameter:
my_object = MyLibraryClass(self.log, self.send_xpl, self.get_stop())
In the class, set the parameter name to callback (so it will be clear for everybody) and store it in an instance variables named self._callback.
class MyLibraryClass:
""" My class
"""
def __init__(self, log, callback, stop):
""" Init MyLibraryClass
@param log : log instance
@param callback : callback
@param stop : stop flag
"""
self._log = log
self._callback = callback
self._stop = stop
Then, you can use this callback in any function when needed. Example:
def a_function(self):
""" My function
"""
# do some things
arg1 = "abc"
arg2 = 234
self._callback(arg1, arg2)
Listen for xPL messages¶
As explained in the binary part, there is nothing to do in the library about catching some xPL messages. The xPL messages are catched in the binary part, they are analysed and to finish, some functions or methods from the library are directly called from the binary part. So, you just need to prepare the needed function with the parameters you need and call these functions from the binary part.
Focus on devices¶
Stop the plugin if no device has been created ?¶
Todo
It depends on the plugin :). If there is devices detection, it should not stop for example. Give some examples
Detected devices¶
In some plugins, the hardware or service is able to see all devices, even if the user didn’t create a Domogik device for it! If so, you should implement the automatic devices detection. So, all detected devices informations will be sent over MQ and the user interfaces will be able to display them.
Readme and changelog files¶
These 2 files are needed in package root directory:
- README.md
- CHANGELOG
README.md¶
The README.md file should contain a quick description of the plugin, and invite the user to find more informations on http://docs.domogik/org/
The .md extension is for Markdown which is a text markup language. You can find more information about Markdown on wikipedia
You may create the file like this:
$ echo " # Purpose
This is a package for Domogik : http://www.domogik.org
Domogik is an open source home automation solution.
# Documentation
You can find the documentation source in the **docs/** folder. When the package will be installed, the documentation will be available in the **Documentation** menu of the Domogik administration for this package.
You may also find online documentation for this plugin. You will be able to find the documentation url on http://repo-public.domogik.org/dashboard
# Install the package
To install this package on your Domogik system, you can go in this GitHub repository releases page and get the link to a release .zip file. Then you just have to do :
dmg_package -i http://path.to/the/file.zip" > README.md
CHANGELOG¶
The real changelog file must be located in docs/changelog.txt. To allow the user to find it quickly, please create a CHANGELOG in the root directory like this:
echo "The changelog informations are available in docs/changelog.txt" > CHANGELOG
Rules for packages release numbers¶
Global rule¶
Here is the main way to choose the version of a Domogik package : <major>.<minor>. If needed, you may use alpha and beta notations : 1.0a1, 1.0b3. In all cases, the version number must respect the PEP 386.
First release of the package¶
The first release of your package should be named 0.1. The following releases should also be named 0.x until the package is fully functional : then you will switch to 1.0 version number. You may want to use some alpha, beta or candidates releases like 1.0a1, 1.0b3, 1.0c1 before using the final 1.0 version number.
Next releases of the package¶
After the 1.0 has been published, if you need to make bugfixes or little evolutions, you may use 1.1, 1.2, ... If you want, you can also use alpha, beta and candidates : 1.1c1.
If you have have to make big evolutions in your package, you may switch to the next major version : 2.0.
Plugin specifications¶
For any development project, the developer (ideally not the developer, but for Domogik plugins, it will be the developer for most of the plugins), must write some specifications.
People often think that specifications takes too much time and it is an optional step. Yes, you can skip the specifications step, but there is a risk you loose a lot of time when doing this ;).
For Domogik plugins we strongly suggest you to write some specifications before doing anything. Here are some things you should focus on:
- plugin features
- xpl messages that will be used (you should read http://xplproject.org.uk/wiki/index.php?title=XPL_Message_Schema)
- how to handle errors (access to the hardware or service, ...)
- does the plugin need dedicated configuration pages on Domoweb administration interface ?
As GitHub already integrates a wiki component for each repository, you should use the plugin repository wiki to write the specifications.
You may also want to define a roadmap in this wiki if you plan to do the plugin in several steps.
start.sh¶
Purpose¶
The start.sh file is only an helper for the developers and people who want to test the plugin from the command line. It will override the PYTHONPATH environment variable and launch the plugin.
Create the start.sh¶
Assuming your plugin name is myplugin, create the start.sh file like this (you just need to update the first line):
$ PLUGIN_ID=myplugin
$ echo "export PYTHONPATH=/var/lib/domogik && /usr/bin/python bin/$PLUGIN_ID.py -f" > start.sh
$ chmod u+x start.sh
Use it¶
To launch the plugin in foreground (it is better during the plugin development), just launch start.sh:
$ cd /var/lib/domogik/domogik_packages/plugin_myplugin
$ ./start.sh
The plugin will be launched in foreground. To stop it, you can use (ctrl)-(C).
Testing a plugin¶
Testing a plugin is really important! This allow to:
- check if the plugin works as designed
- check if the last update didn’t break anything: this is called non regression tests
Some libraries have been created in the Domogik project to help you to create the test scripts. These libraries can:
- create some devices
- configure, start, stop and do some basic checks on the plugin
- help you to test xPL dialogs
Be careful : executing the tests may delete your existing devices and so you can loose some data!!!!
When should the tests be launched ?¶
Well, after each plugin update! But as this could be time consuming, you can automate this step thanks to the testrunner.py tool and Travis, the continuous integration service.
File tree¶
The following files are mandatory for the tests:
tests/
# the 0* files are just helpers for the developers
001_configure.py # this python file is used by the developers to quickly configure the plugin
002_create_device.py # this python file is used by the developers to quickly create some test devices
# all the other files are related to the plugin tests
tests.json # this is a file which describe all the test files. It is used for tests automation
Then, depending on your plugin, you can have only one test file:
tests/
..
tests.py
Or several files:
tests/
..
test_feature_A.py
test_feature_B.py
test_feature_C.py
tests.json¶
This file is very important! It will be used by the testrunner.py tool.
Example:
{
"tests" : {
"alter_configuration_or_setup" : true,
"need_hardware" : false,
"criticity" : "high"
}
}
- alter_configuration_or_setup: true if the test need to alter the plugin configuration or some devices. The test should not be run on a production environment! false if the test doesn’t alter anything and can be run safely on a production environment.
- need_hardware: true if some hardware is needed by the test. Please note that the testrunner.py tool will never run the tests that need some hardware.
- criticity: high, medium or low.
Each test file must be listed in the tests.json file.
A test file¶
A test file is made of 2 parts:
- a class which inherits from PluginTestCase. This class will contain all the test cases related to the plugin.
- the main part which will do some actions and launch the test cases.
Here is a sample file from the teleinfo plugin. This sample file has only one dummy test defined. This is the minimal file you must prepare before creating the tests. This test file will test only global features :
- deletion and creation of devices
- plugin configuration
- plugin startup
- xpl hbeat
- plugin stop successfully
#!/usr/bin/python
# -*- coding: utf-8 -*-
from domogik.xpl.common.plugin import XplPlugin
from domogik.tests.common.plugintestcase import PluginTestCase
from domogik.tests.common.testplugin import TestPlugin
from domogik.tests.common.testdevice import TestDevice
from domogik.tests.common.testsensor import TestSensor
from domogik.common.utils import get_sanitized_hostname
from datetime import datetime
import unittest
import sys
import os
import traceback
class TeleinfoTestCase(PluginTestCase):
def test_0100_dummy(self):
self.assertTrue(True)
if __name__ == "__main__":
### global variables
device = "/dev/teleinfo"
interval = 60
# set up the xpl features
xpl_plugin = XplPlugin(name = 'test',
daemonize = False,
parser = None,
nohub = True,
test = True)
# set up the plugin name
name = "teleinfo"
# set up the configuration of the plugin
# configuration is done in test_0010_configure_the_plugin with the cfg content
# notice that the old configuration is deleted before
cfg = { 'configured' : True }
### start tests
# load the test devices class
td = TestDevice()
# delete existing devices for this plugin on this host
client_id = "{0}-{1}.{2}".format("plugin", name, get_sanitized_hostname())
try:
td.del_devices_by_client(client_id)
except:
print(u"Error while deleting all the test device for the client id '{0}' : {1}".format(client_id, traceback.format_exc()))
sys.exit(1)
# create a test device
try:
#device_id = td.create_device(client_id, "test_device_teleinfo", "teleinfo.electric_meter")
params = td.get_params(client_id, "teleinfo.electric_meter")
# fill in the params
params["device_type"] = "teleinfo.electric_meter"
params["name"] = "test_device_teleinfo"
params["reference"] = "reference"
params["description"] = "description"
# global params
for the_param in params['global']:
if the_param['key'] == "interval":
the_param['value'] = interval
if the_param['key'] == "device":
the_param['value'] = device
print params['global']
# xpl params
pass # there are no xpl params for this plugin
# create
td.create_device(params)
except:
print(u"Error while creating the test devices : {0}".format(traceback.format_exc()))
sys.exit(1)
### prepare and run the test suite
suite = unittest.TestSuite()
# check domogik is running, configure the plugin
suite.addTest(TeleinfoTestCase("test_0001_domogik_is_running", xpl_plugin, name, cfg))
suite.addTest(TeleinfoTestCase("test_0010_configure_the_plugin", xpl_plugin, name, cfg))
# start the plugin
suite.addTest(TeleinfoTestCase("test_0050_start_the_plugin", xpl_plugin, name, cfg))
# do the specific plugin tests
suite.addTest(TeleinfoTestCase("test_0100_dummy", xpl_plugin, name, cfg))
# do some tests comon to all the plugins
suite.addTest(TeleinfoTestCase("test_9900_hbeat", xpl_plugin, name, cfg))
suite.addTest(TeleinfoTestCase("test_9990_stop_the_plugin", xpl_plugin, name, cfg))
# quit
res = unittest.TextTestRunner().run(suite)
if res.wasSuccessful() == True:
rc = 0 # tests are ok so the shell return code is 0
else:
rc = 1 # tests are ok so the shell return code is != 0
xpl_plugin.force_leave(return_code = rc)
A test file : the class which inherits from PluginTestCase¶
In this class, you will define the tests that will be executed on the plugin.
There is a norm to name the functions in this class: test_9999_thetestname.
- test_0xxx_xxx : these functions are reserved, declared in PluginTestCase and related to the preparation of the plugin.
- test_9xxx_xxx : these functions are reserved, declared in PluginTestCase and related to the end of the plugin tests (hbeat test, plugin stop).
- test_1xxx_xxx to test_8xxx_xxx : these functions are free for use.
Dummy example¶
Here is an example of a dummy test which is always good:
class DiskfreeTestCase(PluginTestCase):
def test_0100_dummy(self):
self.assertTrue(True)
Useful functions¶
The following functions can be used for your tests.
Wait for a xPL message¶
self.assertTrue(self.wait_for_xpl(xpltype = "xpl-stat",
xplschema = "sensor.basic",
xplsource = "domogik-{0}.{1}".format(self.name, get_sanitized_hostname()),
data = {"type" : "total_space",
"device" : path,
"current" : du_total},
timeout = interval * 60))
The function self.wait_for_xpl is a blocking function : it will wait until the required xPL message is reveived or until the timeout is reached.
- xpltype : the type of the xPL message waited. Available values are xpl-stat, xpl-trig, xpl-cmnd.
- xplschema : the required xPL schema.
- xplsource : the xPL source of the plugin to test. Always use this value for a plugin : “domogik-{0}.{1}”.format(self.name, get_sanitized_hostname())
- data : a dictionnary with the required content of the xPL message. If you know exactly the value you are waiting for, just add it in the dictionnary. If you can’t guess the value, just put all the known values (device address, ...) in the dictionnary and check after the value with self.xpl_data.data.
- timeout : the timeout in seconds. Once reached, the function will return False as no expected message has been received. Please notice that a 5% margin is allowed, so il you set a timeout of 100 seconds, the function will really use a timeout of 105 seconds : this allows to avoid some errors due to some processing which could, for example, add 1 or 2 seconds between 2 messages.
Once a xPL message is received, its content is stored in self.xpl_data.data. For exemple, to get the value of the key current, you can do:
current_value = self.xpl_data.data['current']
Check the value in inserted in database¶
It is important to check that the values of the received messages are stored in database : a plugin can successfully send some xPL messages but the info.json file can be wrong and this is this file which defines the way to store values in database.
To do this, you must create a TestSensor instance for the device id and the sensor reference. Then, compare the last value of this sensor to the value from the xPL message.
Example:
print(u"Check that the value of the xPL message has been inserted in database")
sensor = TestSensor(device_id, "get_total_space")
self.assertTrue(sensor.get_last_value()[1] == self.xpl_data.data['current'])
Check the time between two xPL messages¶
When a plugin feature should send a xPL message each N seconds, you have to test if the interval is correct. To do this, wait for a first message, get the current time. Wait for another message, get the current time again and check if the difference is correct.
Example (for a message each 2 minutes) :
# get the first message
self.assertTrue(self.wait_for_xpl(xpltype = "xpl-stat",
...
timeout = 2 * 60))
# get the time of the first message
msg1_time = datetime.now()
# get the second message
self.assertTrue(self.wait_for_xpl(xpltype = "xpl-stat",
...
timeout = 2 * 60))
# get the time of the second message
msg2_time = datetime.now()
# check if the interval between the 2 messages is OK
self.assertTrue(self.is_interval_of(2 * 60, msg2_time - msg1_time))
The function self.is_interval_of() takes 2 parameters:
- the expected interval
- the mesured interval
Notice that the function will allow a margin of 5%: if you expect for 100 seconds and there are 105 seconds in the reality, the test will be ok.
Send a xPL message¶
Todo
Explain
Example 1 : wait for a xPL message and check its content¶
The following example if one function from the diskfree plugin tests. It will wait for a given xPL message, check if the value in the xPL message is correct and is inserted in database, wait for a second message, check that the interval between the 2 messages is ok.
Example:
def test_0110_total_space(self):
""" check if the xpl messages about total space are OK
Sample message :
xpl-stat
{
hop=1
source=domogik-diskfree.darkstar
target=*
}
sensor.basic
{
device=/home
type=total_space
current=19465224
}
"""
global interval
global path
global device_id
# get the current total space on the device
du = os.statvfs(path)
du_total = (du.f_blocks * du.f_frsize) / 1024
# do the test
print(u"Check that a message about total space is sent. The message must be received each {0} minute(s)".format(interval))
self.assertTrue(self.wait_for_xpl(xpltype = "xpl-stat",
xplschema = "sensor.basic",
xplsource = "domogik-{0}.{1}".format(self.name, get_sanitized_hostname()),
data = {"type" : "total_space",
"device" : path,
"current" : du_total},
timeout = interval * 60))
print(u"Check that the value of the xPL message has been inserted in database")
sensor = TestSensor(device_id, "get_total_space")
self.assertTrue(sensor.get_last_value()[1] == self.xpl_data.data['current'])
msg1_time = datetime.now()
print(u"Check there is a second message is sent and the interval between them")
self.assertTrue(self.wait_for_xpl(xpltype = "xpl-stat",
xplschema = "sensor.basic",
xplsource = "domogik-{0}.{1}".format(self.name, get_sanitized_hostname()),
data = {"type" : "total_space",
"device" : path,
"current" : du_total},
timeout = interval * 60))
msg2_time = datetime.now()
self.assertTrue(self.is_interval_of(interval * 60, msg2_time - msg1_time))
Example 2 : send a xPL command and check for its response¶
Todo
Example
A test file : the main part¶
Here are the actions that can be done in the main part:
if needed, define some global variables (polling interval, ...). Example:
### global variables interval = 1 path = "/home"
set up the xpl features for the test file. A XplPlugin instance will be created with some special parameters (please always use these parameters, even the generic name). Example:
# set up the xpl features xpl_plugin = XplPlugin(name = 'test', daemonize = False, parser = None, nohub = True, test = True)
set up the plugin name:
# set up the plugin name name = "diskfree"
define the configuration of the plugin. If no configuration is required for the plugin, at least you must set up the configured key to True. Example:
# set up the configuration of the plugin # configuration is done in test_0010_configure_the_plugin with the cfg content # notice that the old configuration is deleted before cfg = { 'configured' : True }
start the common tests by setting up the TestDevice class which helps to manage the devices. Example:
### start tests # load the test devices class td = TestDevice()
if needed, delete all the existing devices of the plugin on the current host. If you do this, you must set alter_configuration_or_setup to True in the json. Example:
# delete existing devices for this plugin on this host client_id = "{0}-{1}.{2}".format("plugin", name, get_sanitized_hostname()) try: td.del_devices_by_client(client_id) except: print(u"Error while deleting all the test device for the client id '{0}' : {1}".format(client_id, traceback.format_exc())) sys.exit(1)
if needed, create some devices. If you do this, you must set alter_configuration_or_setup to True in the json. Notice that the device parameters should come from the global variables defined before. Example (here 2 global parameters are defined but no xpl parameters are defined):
# create a test device try: #device_id = td.create_device(client_id, "test_device_teleinfo", "teleinfo.electric_meter") params = td.get_params(client_id, "teleinfo.electric_meter") # fill in the params params["device_type"] = "teleinfo.electric_meter" params["name"] = "test_device_teleinfo" params["reference"] = "reference" params["description"] = "description" # global params for the_param in params['global']: if the_param['key'] == "interval": the_param['value'] = interval if the_param['key'] == "device": the_param['value'] = device print params['global'] # xpl params pass # there are no xpl params for this plugin # create td.create_device(params) except: print(u"Error while creating the test devices : {0}".format(traceback.format_exc())) sys.exit(1)
then, call the common tests related to the plugin. These tests are common to all plugins and are defined in the class PluginTestCase. The first one will just check if Domogik is running (if not, the plugin will not be able to start). The second one will configure the plugin and the last one will start the plugin. Example:
### prepare and run the test suite suite = unittest.TestSuite() # check domogik is running, configure the plugin suite.addTest(DiskfreeTestCase("test_0001_domogik_is_running", xpl_plugin, name, cfg)) suite.addTest(DiskfreeTestCase("test_0010_configure_the_plugin", xpl_plugin, name, cfg)) # start the plugin suite.addTest(DiskfreeTestCase("test_0050_start_the_plugin", xpl_plugin, name, cfg))
launch all the tests you created in the YourpluginTestCase class. Example:
# do the specific plugin tests suite.addTest(DiskfreeTestCase("test_0110_total_space", xpl_plugin, name, cfg)) suite.addTest(DiskfreeTestCase("test_0120_free_space", xpl_plugin, name, cfg)) suite.addTest(DiskfreeTestCase("test_0130_used_space", xpl_plugin, name, cfg)) suite.addTest(DiskfreeTestCase("test_0140_percent_used", xpl_plugin, name, cfg))
launch some common tests related to the plugin stopping process. The first one will check that the plugin sends hbeat messages and can take several minutes! The second one will try to stop the plugin and check if the plugin can be stopped. Example:
# do some tests common to all the plugins suite.addTest(DiskfreeTestCase("test_9900_hbeat", xpl_plugin, name, cfg)) suite.addTest(DiskfreeTestCase("test_9990_stop_the_plugin", xpl_plugin, name, cfg))
and finally get the status of the tests. If there were some errors, the python test file will return 1. This is very important for the continuous integration tools and testrunner. Example:
# quit res = unittest.TextTestRunner().run(suite) if res.wasSuccessful() == True: rc = 0 # tests are ok so the shell return code is 0 else: rc = 1 # tests are ok so the shell return code is != 0 xpl_plugin.force_leave(return_code = rc)
How to use the serial mock¶
A serial mock can be used to simulate some serial devices and so test the plugin without any hardware!
Test file¶
First, in the test file, define the test_folder variable.
Before
if __name__ == "__main__":
### global variables
After
if __name__ == "__main__":
test_folder = os.path.dirname(os.path.realpath(__file__))
### global variables
Then, in the test file, add the following configuration elements in the main part:
# specific configuration for test mdode (handled by the manager for plugin startup)
cfg['test_mode'] = True
cfg['test_option'] = "{0}/tests_hchp_data.json".format(test_folder)
These are configuration options which will be handled by the plugin itself. The first one will activate the mock and the second one will give the json file which describe the fake device behavior.
Of course, the plugin will need to be adapted to handle these options and the serial mock!
Plugin binary part¶
Add the following parameter when you instantiate your plugin library class:
- self.options.test_option
Example for the teleinfo plugin during the development phasis.
Before
teleinfo_list[device] = Teleinfo(self.log, self.send_xpl, self.get_stop(), device, interval)
After
teleinfo_list[device] = Teleinfo(self.log, self.send_xpl, self.get_stop(), device, interval, self.options.test_option)
Plugin library part¶
Import both serial and serial mock libraries:
import serial as serial
import domogik.tests.common.testserial as testserial
Handle this parameter in your library class:
Example for the teleinfo plugin during the development phases.
Before
class Teleinfo:
def __init__(self, log, callback, stop, device, interval):
...
self._device = device
...
After
class Teleinfo:
def __init__(self, log, callback, stop, device, interval, fake_device):
...
self._device = device
self._fake_device = fake_device
...
Then, when you create the serial device, if this is a fake one, use the serial mock library.
Before
self._ser = serial.Serial(self._device, 1200, bytesize=7,
parity = 'E',stopbits=1)
After
if self._fake_device != None:
self._ser = testserial.Serial(self._fake_device, baudrate=1200, bytesize=7,
parity = 'E',stopbits=1)
else:
self._ser = serial.Serial(self._device, baudrate=1200, bytesize=7,
parity = 'E',stopbits=1)
Create a json serial mock file¶
Now you will have to create the json file. This json file is made of 3 parts:
{
"history" : [ ],
"responses" : {},
"loop" : []
}
Note
Currently, the responses part is not yet implemented
The history part will contain some data coming from the fake real device. All history elements are played only one time, in the order they are defined in the json file. Then, when the history if finished, the loop part will be processed forever.
Here is an example for the history part:
{
"history" : [
...
{ "description" : "Send a start frame flag",
"action" : "data-hex",
"data" : "02"
},
{ "description" : "Send a HP frame",
"action" : "data",
"data" : "\nADCO 030928084432 B\r"
},
...
{ "description" : "wait",
"action" : "wait",
"delay" : 5
},
...
],
"responses" : {},
"loop" : [...]
}
As you can see in the example, each history step is made of 3 parts:
- description : a small description of the step. It will be used for display, but it is mainly used for the developer information.
- action : the type of the action.
- action = “data” : send a string on the fake serial device
- action = “data-hex” : send some hexadecimal data on the fake serial device. Hexadecimal data is written in a human readable mode : 0F44DC...
- action == wait” : send nothing for a while on the fake serial device. The wait time is defined in seconds
- data (for action = data, data-hex) : the data to send
- delay (for action = wait) : the wait time in seconds
Note
The fake serial device will not send data in the first 30 seconds. It allows the plugin to be fully started (MQ ready) so the xpl checks will not missed any xpl message sent before the MQ is ready and send an active status for the plugin (for example).
The loop part is to be defined in the same way as the history part. The only difference is that when the last step of the loop part is reached, then following step will be the first step of the loop part... Just a loop :)
Test runner for Domogik plugins¶
Purpose¶
The test runner tool is used by continuous integration to run a set of test files for a plugin.
How it works ?¶
You just need to give the tool the path to the test files and it will use the tests.json file to execute some test files depending on the parameters you give to the tool.
Here are all the available options:
$ dmg_testrunner -h
usage: dmg_testrunner [-h] [-a] [-c CRITICITY] directory
Launch all the tests that don't need hardware.
positional arguments:
directory What directory to run
optional arguments:
-h, --help show this help message and exit
-a, --allow-alter Launch the tests that can alter the configuration of
the plugin or the setup (devices, ...)
-c CRITICITY, --criticity CRITICITY
Set the minimum level of criticity to use to filter
the tests to execute. low/medium/high. Default is low.
- –allow-alter : you must use this option with caution! It will allow to run all the tests that alter the Domogik system (change the configuration, delete and create some devices, ...). This must NOT be used on a production environment!
- –critivity <level> : set the minimum level of criticity that will be used. If set to high, only the tests tagged as high criticity in the json file will be run.
Example¶
In this example, there is only one test file to launch, which is named tests.
$ dmg_testrunner -a -c low /var/lib/domogik/domogik_packages/plugin_diskfree/tests/
Domogik release : 0.4.1
Running test with the following parameters:
- allow to alter the configuration or setup.
- criticity : low
- path /var/lib/domogik/domogik_packages/plugin_diskfree/tests/
- json file /var/lib/domogik/domogik_packages/plugin_diskfree/tests//tests.json
List of the tests (keep in mind that tests which need hardware will be skipped) :
[ TO RUN ] tests : need hardware=False, alter config or setup=True, criticity=high
---------------------------------------------------------------------------------------
Launching tests
---------------------------------------------------------------------------------------
...
Tests summary :
---------------
Test tests : OK
Travis CI templates¶
What is Travis CI ?¶
Travis CI (https://travis-ci.org/) is a continuous integration tool.
How to add a github repository to Travis CI¶
Enable the repository¶
- Go on the Travis CI website : https://travis-ci.org/
- Click on the link Sign in with GitHub to login thanks to your GitHub account
- Click on your login, then on Accounts
- You will see the list of all your repositories. Set your plugin to ON.
Add a .travis.yml file to your repository root¶
Travis CI uses a file named .travis.yml to execute the tests. This file contains several part and you will find all the needed informations on the Travis CI official website.
For Domogik plugins, you must use this template to create your .travis.yml file.
# This file is used for automated tests with Travis CI : travis-ci.org
# based on the template version 1
# the templates are available in the documentation on http://docs.domogik.org/domogik/dev/en/package_development/plugins/tests/travis_templates.html
#
# Template version : 1
language: python
python:
- "2.7"
mysql:
adapter: mysql2
database: domogik
username: travis
encoding: utf8
env:
DMG_BRANCH=master
DMG_PLUGIN=diskfree
install:
- cd ~
- git clone https://github.com/domogik/domogik.git
- cd domogik
- git checkout $DMG_BRANCH
- ~/domogik/src/domogik/tests/travis/travis-install-dependencies.sh
before_script:
- ~/domogik/src/domogik/tests/travis/travis-setup-database.sh
- ~/domogik/src/domogik/tests/travis/travis-install-domogik-mq.sh
- ~/domogik/src/domogik/tests/travis/travis-install-domogik.sh
- ~/domogik/src/domogik/tests/travis/travis-install-plugin.sh
- sudo ~/domogik/src/domogik/tests/travis/travis-start-domogik.sh
script:
- echo $TRAVIS_BUILD_DIR
- cd $TRAVIS_BUILD_DIR
- dmg_testrunner -a /var/lib/domogik/domogik_packages/plugin_$DMG_PLUGIN/tests/
after_script:
- ~/domogik/src/domogik/tests/travis/travis-after.sh
notifications:
irc: "irc.freenode.net#domogik"
on_success: never
on_failure: always
- Currently, only python 2.7 is tested as Domogik isn’t entirely compliant with python 3.x.
- DMG_BRANCH should be let to master as the master branch of Domogik repository is the last stable version of Domogik.
- DMG_PLUGIN myst be set to the plugin name.
Once this file will be pushed, you will be able to see the test status at https://travis-ci.org/<mylogin>/domogik-plugin-<plugin name>. Example : For example : https://travis-ci.org/fritz-smh/domogik-plugin-diskfree
You can get the plugin status icon at https://travis-ci.org/<mylogin>/domogik-plugin-<plugin name>?branch=<branch name>. For example : https://travis-ci.org/fritz-smh/domogik-plugin-teleinfo.svg?branch=develop
Udev rules files¶
Purpose¶
Each plugin has a udev folder. This folder can contain (if needed) some sample udev rules. There can be only one file if the plugin handles only one hardware interface or many files if many hardware interfaces could be used. In the files, you must add some comments.
Example : onewire-ds9490r.rules¶
This file is for one of the available hardware interfaces handled by the onewire plugin.
# model : DS9490R
# description : Usb DS9490R adaptator
SUBSYSTEMS=="usb", ATTRS{idVendor}=="04fa", ATTRS{idProduct}=="2490", SYMLINK+="onewire", MODE="0666"
- model : related device model.
- description : short description of the rule. Indicate the related device model here.