Developing a Python plugin

From Domoticz
Jump to: navigation, search

Overview

Page (still) under construction.

This page is aimed at developers wishing to improve Domoticz functionality via Python Plugins. If you simply want to use a plugin that a developer has already created for you, please see Using Python plugins

The Domoticz Python Framework is not a general purpose Domoticz extension. It is designed to allow developers to easily create an interface between a piece of Hardware (or Virtual Hardware) and Domoticz. To that end it provides capabilities that seek to do the hard work for the developer leaving them to manage the messages that move backwards and forwards between the two.

The Framework provides the plugin with a full Python 3 instance that exists for as long as Domoticz is running. The plugin is called by Domoticz when relevant events occur so that it can manage connectivity to the hardware and state synchronisation.

Multiple copies of the same plugin can run for users that have more than one instance of a particular hardware type.

The following things should not be attempted using the Python Framework:

  • Use of asynchronous code or libraries. This will not work the way you expect.
  • Use of call back functions. This will not work the way you expect.
  • Waiting or sleeping. Plugins are single threaded so the whole plugin system will wait.

Overall Structure

The plugin documentation is split into two distinct parts:

  • Plugin Definition - Telling Domoticz about the plugin
  • Runtime Structure - Interfaces and APIs to manage message flows between Domoticz and the hardware

Getting started

If you are writing a Python plugin from scratch, you may want to begin by using the Script Template in domoticz/plugins/examples/BaseTemplate.py, which is also available from the github source repo at https://github.com/domoticz/domoticz/blob/master/plugins/examples/BaseTemplate.py

Plugin Definition

To allow plugins to be added without the need for code changes to Domoticz itself requires that the plugins be exposed to Domoticz in a generic fashion. This is done by having the plugins live in a set location so they can be found and via an XML definition embedded in the Python script itself that describes the parameters that the plugin requires.

During Domoticz startup all directories directly under the 'plugins' directory are scanned for python files named plugin.py and those that contain definitions are indexed by Domoticz. When the Hardware page is loaded the plugins defined in the index are merged into the list of available hardware and are indistinguishable from natively supported hardware. For the example below the Kodi plugin would be in a file domoticz/plugins/Kodi/plugin.py. Some example Python scripts can be found in the domoticz/plugins/examples directory.

Plugin definitions expose some basic details to Domoticz and a list of the parameters that users can configure. Each defined parameters will appear as an input on the Hardware page when the plugin is selected in the dropdown.

Definitions look like this:

"""
<plugin key="Kodi" name="Kodi Players" author="dnpwwo" version="1.0.0" wikilink="http://www.domoticz.com/wiki/plugins/Kodi.html" externallink="https://kodi.tv/">
    <params>
        <param field="Address" label="IP Address" width="200px" required="true" default="127.0.0.1"/>
        <param field="Port" label="Port" width="30px" required="true" default="9090"/>
        <param field="Mode1" label="MAC Address" width="150px" required="false"/>
        <param field="Mode2" label="Shutdown Command" width="100px">
            <options>
                <option label="Hibernate" value="Hibernate"/>
                <option label="Suspend" value="Suspend"/>
                <option label="Shutdown" value="Shutdown"/>
                <option label="Ignore" value="Ignore" default="true" />
            </options>
        </param>
        <param field="Mode3" label="Notifications" width="75px">
            <options>
                <option label="True" value="True"/>
                <option label="False" value="False"  default="true" />
            </options>
        </param>
        <param field="Mode6" label="Debug" width="75px">
            <options>
                <option label="True" value="Debug"/>
                <option label="False" value="Normal"  default="true" />
            </options>
        </param>
    </params>
</plugin>
"""

Definition format details:

Tag Description/Attributes
<plugin> Required.
Attribute Purpose
key Required.

Unique identifier for the plugin. Never visible to users this should be short, meaningful and made up of characters A-Z, a-z, 0-9 and -_ only. Stored in the ‘Extra’ field in the Hardware. Used as the base name for the plugin Python file, e.g. ‘DenonAVR’ maps to /plugins/DenonAVR/plugin.py

name Required.

Meaningful description for plugin. Shown in the ‘Type’ drop down on the Hardware page.

version Required.

Plugin version. Used to compare plugin version against repository version to determine if updates are available.

author Optional.

Plugin author. Supplied to Hardware page but not currently shown.

wikilink Optional.

Link to the plugin usage documentation on the Domoticz wiki. Shown on the Hardware page.

externallink Optional.

Link to an external URL that contains additional information about the device. Shown on the Hardware page.

<params> Simple wrapper for param tags. Contained within <plugin>.
<param> Parameter definitions, Contained within <params>.

Parameters are used by the Hardware page during plugin addition and update operations. These are stored in the Hardware table in the database and are made available to the plugin at runtime.

Attribute Purpose
field Required.

Column in the hardware table to store the parameter.
Valid values:

  • SerialPort - used by ‘serial’ transports
  • Address - used by non-serial transports
  • Port - used by non-serial transports
  • Mode1 - General purpose
  • Mode2 - General purpose
  • Mode3 - General purpose
  • Mode4 - General purpose
  • Mode5 - General purpose
  • Mode6 - General purpose
  • Username - Username for authentication
  • Password - Password for authentication
label Required.

Field label. Where possible standard labels should be used so that Domoticz can translate them when languages other than English are enabled.

width Optional.

Width of the field

required Optional.

Marks the field as mandatory and the device will not be added unless a value is provided

default Optional.

Default value for the field. Ignored when <options> are specified.

<options> Simple wrapper for option tags. Contained within <param>.

Parameter definitions that contain this tag will be shown as drop down menus in the Hardware page. Available values are defined by the <option> tag.

<option> Instance of a drop down option. Contained within <options>.
Attribute Purpose
label Required.

Text to show in dropdown menu. Will be translated if a translation exists.

value Required.

Associated value to store in database

default Optional. Valid value:

true

Runtime Structure

Domoticz exposes settings and device details through four Python dictionaries.

Settings

Contents of the Domoticz Settings page as found in the Preferences database table. These are always available and will be updated if the user changes any settings. The plugin is not restarted. They can be accessed by name for example: Settings["Language"]

Parameters

These are always available and remain static for the lifetime of the plugin. They can be accessed by name for example: Parameters["SerialPort"]

Description
Key Unique short name for the plugin, matches python filename.
HomeFolder Folder or directory where the plugin was run from.
Author Plugin Author.
Version Plugin version.
Address IP Address, used during connection.
Port IP Port, used during connection.
Username Username.
Password Password.
Mode1 General Parameter 1
...
Mode6 General Parameter 6
SerialPort SerialPort, used when connecting to Serial Ports.

Devices

Description
Key Unit.

Unit number for the device as specified in the Manifest. Note: Devices can be deleted in Domoticz so not all Units specified will necessarily still be present. E.g: Domoticz.Log(Devices[2].Name)

Function Description
ID The Domoticz Device ID
Name Current Name in Domoticz
DeviceID External device identifier
nValue Current numeric value
sValue Current string value
SignalLevel Numeric signal level
BatteryLevel Numeric battery level
Image Current image number
Type Numeric device type
SubType Numeric device subtype
Used Device Used flag. 1=True, 0=False
LastLevel Last level as reported by Domoticz
LastUpdate Timestamp of the last update, e.g: 2017-01-22 01:21:11
Methods Per device calls into Domoticz to manipulate specific devices
Function Description Example
__init__
Parameter Description
Name Mandatory.
Is appended to the Hardware name to set the initial Domoticz Device name.
This should not be used in Python because it can be changed in the Web UI.
Unit Mandatory.
Plugin index for the Device. This can not change and should be used reference Domoticz devices associated with the plugin. This is also the key for the Devices Dictionary that Domoticz prepopulates for the plugin.
Unit numbers must be less than 256.
TypeName Optional.
Common device types, this will set the values for Type and Subtype.

"Air Quality"
"Alert"
"Barometer"
"Counter Incremental"
"Current/Ampere"
"Current (Single)"
"Custom"
"Distance"
"Gas"
"Humidity"
"Illumination"
"kWh"
"Leaf Wetness"
"Percentage"
"Pressure"
"Rain"
"Selector Switch"
"Soil Moisture"
"Solar Radiation"
"Sound Level"
"Switch"
"Temperature"
"Temp+Hum"
"Temp+Hum+Baro"
"Text"
"Usage"
"UV"
"Visibility"
"Voltage"
"Waterflow"
"Wind"
"Wind+Temp+Chill"

Type Optional.
Directly set the numeric Type value. Should only be used if the Device to be created is not supported by TypeName.
Subtype Optional.
Directly set the numeric Subtype value. Should only be used if the Device to be created is not supported by TypeName.
Switchtype Optional.
Image Optional.
Set the image number to be used with the device. Only required to override the default.
Options Optional.
Set the Device Options field. A few devices, like Selector Switches, require additional details to be set in this field. It is a Python dictionary consisting of key values pairs, where the keys and values must be strings. See the example to the right.
Used Optional

Values
0 (default) Unused
1 Used.
Set the Device Used field. Used devices appear in the appropriate tab(s), unused devices appear only in the Devices page and must be manually marked as Used.

DeviceID Optional.
Set the DeviceID to be used with the device. Only required to override the default which is and eight digit number dervice from the HardwareID and the Unit number in the format "000H000U"
Both positional and named parameters are supported.
Creates a new device object in Python. E.g:
 myDev1 = Domoticz.Device("Total", 1, 113)
 myDev2 = Domoticz.Device(Name="My Counter", Unit=2, TypeName="Counter Incremental")

or

 import Domoticz
 #
 def onStart():
   if (len(Devices) == 0):
       Domoticz.Device(Name="Status",  Unit=1, Type=17,  Switchtype=17).Create()
       Options = {"LevelActions": "||||",
                  "LevelNames": "Off|Video|Music|TV Shows|Live TV",
                  "LevelOffHidden": "false",
                  "SelectorStyle": "1"}
       Domoticz.Device(Name="Source", Unit=2, \
                       TypeName="Selector Switch", Options=Options).Create()
       Domoticz.Device(Name="Volume", Unit=3, \
                       Type=244, Subtype=73, Switchtype=7, Image=8).Create()
       Domoticz.Device(Name="Playing", Unit=4, \
                       Type=244, Subtype=73, Switchtype=7, Image=12).Create()
       Domoticz.Log("Devices created.")

Device objects in Python are in memory only until they are added to the Domoticz database using the Create function documented below.

Create Parameters: None, acts on current object. Creates the device in Domoticz from the object. E.g:
 myDev = Domoticz.Device(Name="Kilo Watts", Unit=16, TypeName="kWh")
 myDev.Create()
 Domoticz.Log("Created device: "+Devices[16].Name+ \
              ", myDev also points to the Device: "+myDev.Name)

or

 Domoticz.Device(Name="Kilo Watts", Unit=16, TypeName="kWh").Create()
 Domoticz.Log("Created device: "+Devices[16].Name)

Successfully created devices are immediately added to the Devices dictionary.

Update Updates the current values in Domoticz.
Parameter Description
nValue Mandatory.

The Numerical device value

sValue Mandatory.

The string device value

Image Optional.

Numeric custom image number

SignalLevel Optional.

Device signal strength, default 100

BatteryLevel Optional.

Device battery strength, default 255

Options Optional.

Dictionary of device options, default is empty {}

Both positional and named parameters are supported.

E.g

 Devices[1].Update(1,”10”)
 Devices[Unit].Update(nValue=nValue, sValue=str(sValue), SignalLevel=50, Image=8)
Delete Parameters: None, acts on current object. Deletes the device in Domoticz.E.g:
 Devices[1].Delete()

or

 myDev = Devices[2]
 myDev.Delete()

Deleted devices are immediately removed from the Devices dictionary but local instances of the object are unchanged.

Refresh Parameters: None. Refreshes the values for the device from the Domoticz database.

Not normally required because device values are updated when callbacks are invoked.

Images

Developers can ship custom images with plugins in the standard Domoitcz format as described here: [1]. Resultant zip file(s) should be placed in the folder with the plugin itself

Description
Key Base.

The base value as specified in icons.txt file in custom image zip file.

Function Description
ID Image ID in CustomImages table
Name Name as specified in upload file
Base This MUST start with (or be) the plugin key as defined in the XML definition. If not the image will not be loaded into the Images dictionary.
Description Description as specified in upload file
Methods Per image calls into Domoticz to manipulate specific images
Function Description
__init__
Parameter Description
Filename Mandatory.
The zip file name containing images formatted for Domoticz.
Both positional and named parameters are supported.
Creates a new image object in Python.

E.g To allow users to select from three device icons in the hardware page create three zip files with different bases:

 myPlugin Icons.zip -> icons.txt
 myPlugin;Plugin;Plugin Description

and

 myPlugin1 Icons.zip -> icons.txt
 myPluginAlt1;Plugin;Plugin Description

and

 myPlugin2 Icons.zip -> icons.txt
 myPluginAlt2;Plugin;Plugin Description

and an XML parameter declaration like:

 <plugin key="myPlugin" name="myPlugin cool hardware" version="1.5.0" >
   <params>
       <param field="Mode1" label="Icon" width="100px">
           <options>
               <option label="Blue"  value="myPlugin" default="true" />
               <option label="Black" value="myPluginAlt1"/>
               <option label="Round" value="myPluginAlt2"/>
           </options>
       </param>
       ...

then load the images into Domoticz at startup if they are not there and set the

   def onStart(self):
       if ("myPlugin"     not in Images):
           Domoticz.Image('myPlugin Icons.zip').Create()
       if ("myPluginAlt1" not in Images):
           Domoticz.Image('myPlugin1 Icons.zip').Create()
       if ("myPluginAlt2" not in Images):
           Domoticz.Image('myPlugin2 Icons.zip').Create()
       
       if (1 in Devices) and (Parameters["Mode1"] in Images):
           if (Devices[1].Image != Images[Parameters["Mode1"]].ID):
               Devices[1].Update(nValue=Devices[1].nValue, \ 
                                 sValue=str(Devices[1].sValue), \
                                 Image=Images[Parameters["Mode1"]].ID)

Image objects in Python are in memory only until they are added to the Domoticz database using the Create function documented below.

Create Parameters: None, acts on current object. Creates the image in Domoticz from the object. E.g:
 myImg = Domoticz.Image(Filename="Plugin Icons.zip")
 myImg.Create()

or

 Domoticz.Image(Filename="Plugin Icons.zip").Create()

Successfully created images are immediately added to the Images dictionary.

Delete Parameters: None, acts on current object. Deletes the image in Domoticz.E.g:
 Images['myPlugin'].Delete()

or

 myImg = Images['myPlugin']
 myImg.Delete()

Deleted images are immediately removed from the Images dictionary but local instances of the object are unchanged.

Callbacks

Plugins are event driven. Domoticz will notify the plugin when certain events occur through a number of callbacks, these are:

Callback Description
onStart Parameters: None.

Called when the hardware is started, either after Domoticz start, hardware creation or update.

onConnect Parameters: Status, Description

Called when connection to remote device either succeeds or fails. Zero Status indicates success. If Status is not zero then the Description will describe the failure.

onMessage Parameters: Data, Status, Extras.

Called when a single,complete message is received from the external hardware (as defined by the Protocol setting). This callback should be used to interpret messages from the device and set the related Domoticz devices as required. Status and Extras are protocol specific. For HTTP Status is the Response Status (200, 302, 404 etc) and Extras is a dictionary containing the Response Headers

onNotification Parameters: Name, Subject, Text, Status, Priority, Sound, ImageFile.

Called when any Domoticz device generates a notification. Name parameter is the device that generated the notification, the other parameters contain the notification details. Hardware that can handle notifications should be notified as required.

onCommand Parameters: Unit, Command, Level, Hue.

Called when a command is received from Domoticz. The Unit parameters matches the Unit specified in the device definition and should be used to map commands to Domoticz devices. Level is normally an integer but may be a floating point number if the Unit is linked to a thermostat device. This callback should be used to send Domoticz commands to the external hardware.

onHeartbeat Called every 'heartbeat' seconds (default 10) regardless of connection status.

Heartbeat interval can be modified by the Heartbeat command. Allows the Plugin to do periodic tasks including request reconnection if the connection has failed.

onDisconnect Called after the remote device is disconnected.
onStop Called when the hardware is stopped or deleted from Domoticz.

C++ Callable API

Importing the ‘Domoticz’ module in the Python code exposes functions that plugins can call to perform specific functions. All functions are non-blocking and return immediately.

Function Description/Attributes
Debug Parameters: String

Write message to Domoticz log only if verbose logging is turned on.

Log Parameters: String.

Write message to Domoticz log

Error Parameters: String

Write error message to Domoticz log

Debugging Parameters: Integer, 0 or 1.

Set logging level. 1 set verbose logging, all other values use default level

Transport Defines the connection type that will be used by the plugin.
Parameter Description
Transport Mandatory.
Values can be TCP/IP or Serial.
Address Mandatory.
IP Address or SerialPort to connect to.
Port Optional.
TCP/IP connections only, string containing the port number.
Baud Optional.
Serial connections only, the required baud rate.

Default: 115200

This allows Domoticz to make connections on behalf of the plugin. E.g:

 Domoticz.Transport("TCP/IP", Parameters["Address"], Parameters["Port"])
 Domoticz.Transport(Transport="Serial", Address=Parameters["SerialPort"], Baud=115200)

Both positional and named parameters are supported.

Protocol Parameters: String.

Defines the protocol details that will be used by the plugin. This allows Domoticz to know how to handle incoming messages properly.

Attribute Purpose
protocol Required.

The protocol that will be used to talk to the external hardware. This is used to allow Domoticz to break incoming data into single messages to pass to the plugin. Valid values:

  • None (default)
  • line
  • JSON
  • XML
  • HTTP
Heartbeat Parameters: Integer.

Set the heartbeat interval in seconds, default 10 seconds. Values greater than 30 seconds will cause a message to be regularly logged about the plugin not responding. The plugin will actually function correctly with values greater than 30 though.

Connect Parameters: None.

Initiate a connection to a external hardware using transport details. Connect returns immediately and the results will be returned via the onConnect callback. If the address set via the Transport function translates to multiple endpoint they will all be tried in order until the connection succeeds or the list of endpoints is exhausted.
Note that I/O operations immediately after a Connect (but before onConnect is called) may fail because the connection is still in progress so is technically not connected.

Send Send the specified message to the external hardware.
Parameter Description
Message Mandatory.
Message text to send.
Verb Optional.
Protocol specific meaning. For HTTP should be GET or POST.
URL Optional.
Protocol specific meaning. For HTTP should be the target URL.
Headers Optional.
A dictionary with Protocol specific meaning. For HTTP should be a dictionary of HTTP headers.
Delay Optional.
Number of seconds to delay message send.
Note that Domoticz will send the message sometime after this period. Other events will be processed in the intervening period so delayed sends will be processed out of order. This feature may be useful during delays when physical devices turn on.

Both positional and named parameters are supported.
E.g:

 Domoticz.Send(Message=myMessage, Delay=4)
 
 Headers = {"Connection": "keep-alive", "Accept": "Content-Type: text/html; charset=UTF-8"}
 Domoticz.Send("", "GET", "/page.html", Headers)
 
 postData = "param1=value&param2=other+value"
 Domoticz.Send(postData, "POST", "/MediaRenderer/AVTransport/Control", {'Content-Length':str(len(postData))})
Disconnect Parameters: None.

Disconnect from the external hardware

Notifier Parameters: Name, String.

Informs the plugin framework that the plugin's external hardware can comsume Domoticz Notifications.
When the plugin is active the supplied Name will appear as an additional target for Notifications in the standard Domoticz device notification editing page. The plugin framework will then call the onNotification callback when a notifiable event occurs.

Examples

There are a number of examples that are available that show potential usages and patterns that may be useful:

Example Description
Base Template A good starting point for developing a plugin.
Denon 4306 Support for Denon AVR amplifiers.
  • TCP/IP connectivity over Telnet using Line protocol.
  • Sending delays when the amplifier is powering on.
  • Dynamic creation of Selector Switches
DLink DSP-W215 TCP/IP connectivity using HTTP protocol.
  • Features use of SOAP to demonstrate use of HTTP headers
Kodi Alternate Kodi plugin to the built in one.
  • TCP/IP connectivity and use of the JSON protocol.
  • Features three additional devices in Domoticz
RAVEn Alternate RAVEn Energy Monitor plugin to the built in one.
  • Serial Port use with the XML protocol

Debugging

Debugging embedded Python can be done using the standard pdb functionality that comes with Python using the rpdb (or 'Remote PDB') Python library.

The only restriction is that it can not be done from a debug instance of Domoticz running in Visual Studio.

Download the rpdb library from https://pypi.python.org/pypi/rpdb/ and drop the 'rpdb' directory into the directory of the plugin to be debugged (or anywhere in the Python path). Import the library using something like:

   def onStart(self):
       if Parameters["Mode6"] == "Debug":
           Domoticz.Debugging(1)
           Domoticz.Log("Debugger started, use 'telnet 0.0.0.0 4444' to connect")
           import rpdb
           rpdb.set_trace()
       else:
         Domoticz.Log("onStart called")

The rpdb.set_trace() command causes the Python Framework to be halted and a debugger session to be started on port 4444. For the code above the Domoticz log will show something like:

 2017-04-17 15:39:25.448  (MyPlugin) Initialized version 1.0.0, author 'dnpwwo'
 2017-04-17 15:39:25.448  (MyPlugin) Debug log level set to: 'true'.
 2017-04-17 15:39:25.448  (MyPlugin) Debugger started, use 'telnet 0.0.0.0 4444' to connect
 pdb is running on 127.0.0.1:4444

Connect to the debugger using a command line tool such as Telnet. Attaching to the debugger looks like this if you start the session on the same machine as Domoticz, otherwise supply the Domoticz IP address instead of '0.0.0.0':

[email protected]:~$ telnet 0.0.0.0 4444 
Trying 0.0.0.0...
Connected to 0.0.0.0.
Escape character is '^]'.
--Return--
> /home/pi/domoticz/plugins/MyPlugin/plugin.py(30)onStart()->None
-> rpdb.set_trace()
(Pdb)

enter commands at the prompt such as:

(Pdb) l  
 25  	    def onStart(self):
 26  	        if Parameters["Mode6"] == "Debug":
 27  	            Domoticz.Debugging(1)
 28  	            Domoticz.Log("Debugger started, use 'telnet 0.0.0.0 4444' to connect")
 29  	            import rpdb
 30  ->	            rpdb.set_trace()
 31  	        else:
 32  	          Domoticz.Log("onStart called")
 33  	
 34  	    def onStop(self):
 35  	        Domoticz.Log("onStop called")
(Pdb) pp Parameters
 {'Address': ,
 'Author': 'dnpwwo',
 'HardwareID': 7,
 'HomeFolder': '/home/pi/domoticz/plugins/MyPlugin/',
 'Key': 'Debug',
 'Mode1': ,
 'Mode2': ,
 'Mode3': ,
 'Mode4': ,
 'Mode5': ,
 'Mode6': 'Debug',
 'Name': 'MyPlugin',
 'Password': ,
 'Port': '4444',
 'SerialPort': ,
 'Username': ,
 'Version': '1.0.0'}
(Pdb) b 53
Breakpoint 1 at /home/pi/domoticz/plugins/MyPlugin/plugin.py:53
(Pdb) continue
> /home/pi/domoticz/plugins/MyPlugin/plugin.py(53)onHeartbeat()
-> Domoticz.Log("onHeartbeat called")
(Pdb) ll
 52  	    def onHeartbeat(self):
 53 B->	        Domoticz.Log("onHeartbeat called")
(Pdb) 

Details of debugging commands can be found here: https://docs.python.org/3/library/pdb.html#debugger-commands