Developing a Python plugin
Note: This page is maintained in the Domoticz GitHub repository. Please do not edit it directly on the Wiki.
Revision: 2026-07-03
Overview
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. To see the Plugins already developed (for a reference) got to page 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.
Plugin Framework types
There are current two variations of the Plugin Framework available, plugin authors can use either but not both at the same time:
- Legacy Framework: This has been stable for several years, invoked by importing 'Domoticz' into the plugin
- Extended Framework:
- Built on Legacy Framework but has a new object model and event localisation
- Invoked by importing 'DomoticzEx' into the plugin.
- Creates a two layer mapping over the DeviceStatus table of Devices and Units
- Plugins are not restricted to 256 Units (each Device can have 256 Units)
- Supports encapsulation and limited polymorphism
Sleeping and multi-threading details:
The following things should not be attempted using the Python Framework:
- Waiting or sleeping in Domoticz callbacks. Plugin callbacks are single threaded so the whole plugin system will wait.
The Python Framework has been uplifted to support multi-threaded plugins, key points:
- Use of asynchronous code or modules and callback functions are now supported. These should function as expected.
- All threads started within the plugin (either directly or by imported modules) must be terminated by the plugin prior to the plugin stopping ('onStop' is the recommended place for this). Failure to do this will result in Python aborting Domoticz during hardware 'Stops' &/or 'Updates'. The Plugin Framework cannot enumerate threads started by the plugin or stop them (Python limitation) and any active threads when the plugin interpreter is destroyed will cause Python to abort. YOU HAVE BEEN WARNED !
An example of a multi-threaded plugin including how to show running threads and thread shutdown can be found here on Github
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/development/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/">
<description>
<h2>Kodi Media Player Plugin</h2><br/>
<h3>Features</h3>
<ul style="list-style-type:square">
<li>Comes with three selectable icon sets: Default, Black and Round</li>
<li>Display Domoticz notifications on Kodi screen if a Notifier name is specified and events configured for that notifier</li>
<li>Multiple Shutdown action options</li>
<li>When network connectivity is lost the Domoticz UI will optionally show the device(s) with a Red banner</li>
</ul>
<h3>Devices</h3>
<ul style="list-style-type:square">
<li>Status - Basic status indicator, On/Off. Also has icon for Kodi Remote popup</li>
<li>Volume - Icon mutes/unmutes, slider shows/sets volume</li>
<li>Source - Selector switch for content source: Video, Music, TV Shows, Live TV, Photos, Weather</li>
<li>Playing - Icon Pauses/Resumes, slider shows/sets percentage through media</li>
</ul>
</description>
<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">
<description><h2>Debugging</h2>Select the desired level of debug messaging</description>
<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.
| ||||||||||||||||
| <description> | HTML description, contained within <plugin> and <param> tags.
This is displayed in the Hardware page when configuring a plugin. Embedded HTML tags are allowed and respected. | ||||||||||||||||
| <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.
| ||||||||||||||||
| <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>.
|
Extended Parameter Settings
In addition to the basic <param> attributes documented above, the parameter definition supports a richer set of input types, validation hints, conditional visibility, and grouping. These extended settings let the Hardware page render plugin configuration as proper number inputs, checkboxes, sliders, password fields, and collapsible groups instead of plain text boxes.
A complete working example is shipped with Domoticz at plugins/examples/TestExtendedSettings/plugin.py.
New <param> attributes
| Attribute | Purpose |
|---|---|
| type | Optional. Selects the input control rendered on the Hardware page. Valid values:
If |
| min | Optional. Minimum value for number and slider types.
|
| max | Optional. Maximum value for number and slider types.
|
| step | Optional. Increment used by number and slider types.
|
| password | Optional. password="true" masks the value during entry and on subsequent views of the Hardware page. Works on any text field, not just the Password column.
|
| visible_when | Optional. Conditional visibility expression of the form OtherField=value. The field is shown only when the referenced parameter currently equals the given value. Useful for revealing fields that only make sense for a particular protocol or mode (e.g. show a certificate path only when Protocol=https).
|
| default | Optional. Default value used when the plugin is first added. For boolean use "true"/"false"; for number/slider use a numeric string. For dropdowns the option whose value matches the default is preselected, unless a per-option default="true" is set (which takes precedence).
|
<group>
Parameters can be wrapped in a <group label="..."> block to render them as a collapsible section on the Hardware page. Groups can be used to hide advanced or rarely-changed options behind a single click. Groups contain one or more <param> elements and may be mixed with ungrouped parameters in the same <params> block.
| Attribute | Purpose |
|---|---|
| label | Required. Heading shown for the group. Translated if a translation exists. |
Worked example
The following plugin definition (from plugins/examples/TestExtendedSettings/plugin.py) demonstrates every extended setting:
"""
<plugin key="TestExtSettings" name="Test Extended Settings Plugin" author="test" version="1.0.0">
<description>
<h2>Test Extended Settings</h2><br/>
This plugin is for testing the extended plugin settings feature.
</description>
<params>
<param field="Address" label="IP Address" width="200px" required="true" default="127.0.0.1"/>
<param field="Port" label="Port" width="100px" required="true" default="8080"/>
<param field="Interval" type="number" label="Poll Interval (s)" min="5" max="3600" step="5" default="30" width="100px"/>
<param field="EnableDebug" type="boolean" label="Debug Mode" default="false"/>
<param field="Brightness" type="slider" label="Default Brightness" min="0" max="100" default="50" width="200px"/>
<param field="Protocol" label="Protocol" width="150px">
<options>
<option label="HTTP" value="http" default="true"/>
<option label="HTTPS" value="https"/>
</options>
</param>
<param field="Certificate" label="Certificate Path" width="200px" visible_when="Protocol=https" default=""/>
<param field="ApiKey" label="API Key" password="true" width="200px" default=""/>
<group label="Advanced Settings">
<param field="RetryCount" type="number" label="Retry Count" min="0" max="10" default="3" width="100px"/>
<param field="Timeout" type="number" label="Timeout (s)" min="1" max="60" default="10" width="100px"/>
<param field="EnableNotifications" type="boolean" label="Enable Notifications" default="true"/>
</group>
<param field="Mode6" label="Legacy Debug" width="150px">
<options>
<option label="None" value="0" default="true"/>
<option label="All" value="-1"/>
</options>
</param>
</params>
</plugin>
"""
Notes:
- All values still arrive in the plugin via the
Parametersdictionary as strings — convert withint(),float(), or compare against"true"/"false"as appropriate. visible_whenonly controls UI visibility; the underlying value is still stored and exposed inParameters.- Extended attributes are ignored by older Domoticz versions, so a plugin that uses them remains parseable; the fields will simply render as plain text inputs.
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. |
| Name | Name assigned by the user to the hardware. |
| 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. |
| DomoticzVersion | Domoticz version, for instance 2025.2.17303 |
| DomoticzHash | Domoticz hash, for instance a06400e05 |
| DomoticzBuildTime | Domoticz build time, for instance 2020-03-03 15:41:00 |
Extended Plugin Framework
Available in Domoticz Stable 2022.1!
Devices
| Description | |||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Members | DeviceID.
Devices are a container class designed to hold the Units that share a common DeviceID
| ||||||||||||
| Methods | Per device calls into Domoticz to manipulate specific devices
| ||||||||||||
| Callbacks | If defined, these callbacks will be sent to the Device object rather than to the module level function. Note that the function definitions are different because the API only passes the required parameters so no Device details are passed.
To define these callback plugin authors need to register a local object that extends the Domoticz default. See the Register function in the #C.2B.2B_Callable_API section for details
import DomoticzEx as Domoticz
...
class hvacDevice(Domoticz.Device):
def __init__(self, DeviceID):
super().__init__(DeviceID)
def onDeviceAdded(self, Unit):
Domoticz.Log("Device onDeviceAdded for Unit: "+str(self.Units[Unit]))
def onDeviceModified(self, Unit):
Domoticz.Log("Device onDeviceModified for Unit: "+str(self.Units[Unit]))
def onDeviceRemoved(self, Unit):
Domoticz.Log("Device onDeviceRemoved for Unit: "+str(self.Units[Unit]))
def onCommand(self, Unit, Command, Level, Hue):
Domoticz.Log("onCommand called for Device '" + DeviceID + "', Unit " + str(Unit) + ": Parameter '" + str(Command) + "', Level: " + str(Level))
Command = Command.strip()
action, sep, params = Command.partition(' ')
action = action.capitalize()
# Override the default Domoticz device object with custom one
Domoticz.Register(Device=hvacDevice)
def onDeviceModified(self, DeviceID, Unit):
# This will never be called because the Device specific version overrides it
Domoticz.Log("Device onDeviceModified")
|
Units
| Description | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Members | Unit.
Unit number for the device as specified in the Manifest.
Note: Units can be deleted in Domoticz so not all Units specified will necessarily still be present.
E.g:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Methods | Per device calls into Domoticz to manipulate specific devices
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Callbacks |
|
Legacy Plugin Framework
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:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Methods | Per device calls into Domoticz to manipulate specific devices
|
Available Device Types
Filling is in progress, table doesn't contain full available list yet. Look at the Domoticz API/JSON page section Update Devices for more background information.
| Type | Subtype | TypeName | Description | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ID | Name | ID | Name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 17 | Lighting 2 | Behaves the same as Light/Switch, Preferable to use Type 244 instead | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 80 | Temp | 5 | LaCrosse TX3 | Temperature | Temperature sensor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 81 | Humidity | 1 | LaCrosse TX3 | Humidity | Humidity sensor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 82 | Temp+Hum | 1 | LaCrosse TX3 | Temp+Hum | Temperature + Humidity sensor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 84 | Temp+Hum+Baro | 1 | THB1 - BTHR918, BTHGN129 | Temp+Hum+Baro | Temperature + Humidity + Barometer sensor Device.Update(nValue, sValue) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 2 | THB2 - BTHR918N, BTHR968 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 16 | Weather Station | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 85 | Rain | 1 | Rain | Rain sensor (sValue: "<RainLastHour_mm*100>;<Rain_mm>", Rain_mm is everincreasing counter) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 86 | Wind | 1 | Wind | Wind sensor (sValue: "<WindDirDegrees>;<WindDirText>;<WindAveMeterPerSecond*10>;<WindGustMeterPerSecond*10>;<Temp_c>;<WindChill_c>") | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 4 | Wind+Temp+Chill | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 87 | UV | 1 | UV | UV sensor (sValue: "<UV>;<Temp>") | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 89 | Current | 1 | Current/Ampere | Ampere (3 Phase) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 93 | Scale | 1 | Weight | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 113 | Counter | 0 | RFXMeter
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 241 | Color Switch | 1 | RGBW | WW | RGB + white, either RGB or white can be lit | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 2 | RGB | RGB | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 3 | White | White | Monochrome white | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 4 | RGBWW | RGB_CW_WW | RGB + cold white + warm white, either RGB or white can be lit | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 6 | RGBWZ | RGB_W_Z | Like RGBW, but allows combining RGB and white | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 7 | RGBWWZ | RGB_CW_WW_Z | Like RGBWW, but allows combining RGB and white | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 8 | Cold white + Warm white | CW_WW | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 242 | Thermostat (beta 2023.2: Setpoint) | 1 | Setpoint | Set Point | from Beta 2023/2 dd18-9-2023: name change to Setpoint and added Options.
Options={'ValueStep':'0.5', ' ValueMin':'-200', 'ValueMax':'200', 'ValueUnit':'°C'} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 243 | General | 1 | Visibility | Visibility | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 2 | Solar Radiation | Solar Radiation | sValue: "float" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 3 | Soil Moisture | Soil Moisture | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 4 | Leaf Wetness | Leaf Wetness | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 6 | Percentage | Percentage | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 7 | Fan | Fan | Fan speed in rpm (from version 2024.3.16011) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 8 | Voltage | Voltage | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 9 | Pressure | Pressure | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 19 | Text | Text | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 22 | Alert | Alert | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 23 | Ampere (1 Phase) | Current (Single) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 24 | Sound Level | Sound Level | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 26 | Barometer | Barometer | nValue: 0, sValue: "pressure;forecast" Forecast: 0 - Stable 1 - Sunny 2 - Cloudy 3 - Unstable 4 - Thunderstorm 5 - Unknown 6 - Cloudy/Rain | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 27 | Distance | Distance | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 28 | Counter Incremental | Counter Incremental | Incremental counter used to measure energy (used or produced), gas, water. Does not compute the power or flow rate. nValue=0, sValue=INCREMENT_VALUE to increase counter by INCREMENT_VALUE, or sValue=NEGATIVE_VALUE to reset the counter.
A counter divider parameter can be specified: for example, for water flux meters, it's possible to set the divider to 1000 in case that the water flux sensor provides 1 pulse every liter, or 100000 if it sends 1 pulse every 0.01 liter. sValue parameter is float time, so it's possible for example to set divider to 1000 (1 liter) and update the device for a smaller quantity, for example sValue=0.002 to increase counter for 2 ml. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 29 | kWh | kWh | Electric (Instant+Counter) nValue should be zero sValue are two numbers separated by semicolon like "123;123456" The first number is the actual power in Watt, the second number is actual energy in Wh. When the option "EnergyMeterMode" is set to "Calculated", the second value is ignored When creating the device, set Switchtype=4 to get a device exporting energy (instead of importing) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 30 | Waterflow | Waterflow | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 31 | Custom Sensor | Custom | nValue: 0, sValue: "floatValue", Options: {'Custom': '1;<axisUnits>'} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 33 | Managed counter | nValue is always 0 sValue must be 2 semicolon separated values to update Dashboard, for instance "123456;78", 123456 being the absolute counter value, 78 being the usage (Wh). Set counter to -1 if you can't know the counter absolute value
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 244 | Light/Switch | 62 | Selector Switch | Selector Switch | Additional attribute Switchtype is applicable
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 73 | Switch | Check:
Additional attribute Switchtype | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 246 | Lux | 1 | Lux | Illumination | Illumination (sValue: "float") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 247 | Temp+Baro | 1 | LaCrosse TX3 | Temperature + Barometer sensor | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 248 | Usage | 1 | Electric | Usage | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 249 | Air Quality | 1 | Air Quality | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 250 | P1 Smart Meter | 1 | Energy | nValue=0, sValue="USAGE1;USAGE2;RETURN1;RETURN2;CONS;PROD" see for more info Domoticz API/JSON URL's#Electricity P1 smart meter | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 251 | P1 Smart Meter | 2 | Gas | Gas | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Note on counters
Use DisableLogAutoUpdate option
Usually, counters are updated daily to feed week/month/year history log views. Starting version 4.11774, if you want to disable that behavior to control everything from an external script or a plugin (already the case for managed counter), you can set the "DisableLogAutoUpdate" device option to "true", for instance in a Python plugin:
Domoticz.Device(Name="MyCounter", Unit=1, Type=0xfa, Subtype=0x01, Options={"DisableLogAutoUpdate" : "true").Create()
Use AddDBLogEntry option
Starting version 4.11774, you can too directly insert data in in history log. Set the "AddDBLogEntry" device option to "true", for instance in a Python plugin:
Domoticz.Device(Name="MyCounter", Unit=1, Type=0xfa, Subtype=0x01, Options={"AddDBLogEntry" : "true").Create()
Then, depending on counters, you can insert values in history log. For most counters:
Devices[Unit].Update(nValue=nValue, sValue="COUNTER;USAGE;DATE")
- COUNTER = absolute counter energy (Wh)
- USAGE = energy usage in Watt-hours (Wh)
- DATE = date with %Y-%m-%d format (for instance 2019-09-24) to put data in last week/month/year history log, or "%Y-%m-%d %H:%M:%S" format (for instance 2019-10-03 14:00:00) to put data in last days history log
For multi meters (P1 Smart Meter, CM113, Electrisave and CM180i):
Devices[Unit].Update(nValue=nValue, sValue="USAGE1;USAGE2;RETURN1;RETURN2;CONS;PROD;DATE")
- USAGE1= energy usage meter tariff 1, This is an incrementing counter
- USAGE2= energy usage meter tariff 2, This is an incrementing counter
- RETURN1= energy return meter tariff 1, This is an incrementing counter
- RETURN2= energy return meter tariff 2, This is an incrementing counter
- CONS= actual usage power (Watt)
- PROD= actual return power (Watt)
- DATE = date with %Y-%m-%d format (for instance 2019-09-24) to put data in last week/month/year history log, or "%Y-%m-%d %H:%M:%S" format (for instance 2019-10-03 14:00:00) to put data in last days history log
or
Devices[Unit].Update(nValue=nValue, sValue="USAGE1;USAGE2;RETURN1;RETURN2;CONS;PROD;COUNTER1;COUNTER2;COUNTER3;COUNTER4;DATE")
as previously, plus absolute counter values
for counters with custom units: Use RFXCom customer counter, e.g. Domoticz.Unit(Name=name, Unit=unit, Type=113, Switchtype=3, Options={"ValueQuantity": "Custom", "ValueUnits": "customunit"}).Create()
Connections
Connection objects allow plugin developers to connect to multiple external sources using a variety of transports and protocols simultaneously. By using the plugin framework to handle connectivity Domoticz will remain responsive no matter how many connections the plugin handles.
Connections remain active only while they are in scope in Python after that Domoticz will actively disconnect them so plugins should store Connections that they want to keep in global or class variables.
| Function | Description/Attributes | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| __init__ | Defines the connection type that will be used by the object.
This allows Domoticz to make connections on behalf of the plugin. E.g: myConn = Domoticz.Connection(Name="JSON Connection", Transport="TCP/IP", Protocol="JSON", Address=Parameters["Address"], Port=Parameters["Port"])
myConn.Connect()
secureConn = Domoticz.Connection(Name="Secure Connection", Transport="TCP/IP", Protocol="HTTPS", Address=Parameters["Address"], Port="443")
secureConn .Connect()
mySerialConn = Domoticz.Connection(Name="Serial Connection", Transport="Serial", Protocol="XML", Address=Parameters["SerialPort"], Baud=115200)
mySerialConn.Connect()
Both positional and named parameters are supported. | ||||||||||||||
| Name | Returns the Name of the Connection. | ||||||||||||||
| Address | Return/Set the Address associated with the Connection. | ||||||||||||||
| Port | Return/Set the Port associated with the Connection. | ||||||||||||||
| Baud | Returns the Baud Rate of the Connection. | ||||||||||||||
| Target | Get or Set the event target for the Connection. See Target parameter description on Connect and Listen for more detail. | ||||||||||||||
| Parent | Normally 'None' but for incoming connections this will hold the Connection object that is 'Listening' for the connection. | ||||||||||||||
| Connecting | Parameters: None.
Returns True if a connection has been requested but has yet to complete (or fail), otherwise False. | ||||||||||||||
| Connected | Parameters: None.
Returns True if the connection is connected or listening, otherwise False. | ||||||||||||||
| Connect | Initiate a connection to a external hardware using transport details.
Connect returns immediately and the results of the actual connection operation will be returned via the onConnect callback. If the address set via the Transport function translates to multiple endpoints they will all be tried in order until the connection succeeds or the list of endpoints is exhausted. | ||||||||||||||
| Listen | Start listening on specifed Port using the specified TCP/IP, UDP/IP or ICMP/IP transport.
Connection objects will be created for each client that connects and onConnect will be called. self.httpServerConn = Domoticz.Connection(Name="WebServer", Transport="TCP/IP", Protocol="HTTP", Port=Parameters["Port"])
self.httpServerConn.Listen()
self.BeaconConn = Domoticz.Connection(Name="Beacon", Transport="UDP/IP", Address="239.255.255.250", Port="1900")
self.BeaconConn.Listen()
| ||||||||||||||
| Send | Send the specified message to the external hardware.
Both positional and named parameters are supported. myConn.Send(Message=myMessage, Delay=4)
Headers = {"Connection": "keep-alive", "Accept": "Content-Type: text/html; charset=UTF-8"}
myConn.Send({"Verb":"GET", "URL":"/page.html", "Headers": Headers})
postData = "param1=value¶m2=other+value"
myHttpConn.Send({'Verb':'POST', 'URL':'/MediaRenderer/AVTransport/Control', 'Data': postData})
responseData = "<!doctype html><html><head></head><body><h1>Successful GET!!!</h1><body></html>"
self.httpClientConn.Send({"Status":"200 OK", "Headers": {"Connection": "keep-alive", "Accept": "Content-Type: text/html; charset=UTF-8"}, "Data": responseData })
udpBcastConn = Domoticz.Connection(Name="UDP Broadcast Connection", Transport="UDP/IP", Protocol="None", Address="224.0.0.1", Port="9777")
udpBcastConn.Send("Hello World!")
| ||||||||||||||
| Disconnect | Parameters: None.
Terminate the connection to the external hardware for the connection. | ||||||||||||||
| BytesTransferred | Parameters: None.
Returns the number of bytes transferred since the connection was opened. | ||||||||||||||
| LastSeen | Parameters: None.
Returns a formatted timestamp string ("YYYY-MM-DD HH:MM:SS") indicating when data was last seen on the connection. |
Images
Developers can ship custom images with plugins in the standard Domoticz 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.
| |||||||||||||||
| Methods | Per image calls into Domoticz to manipulate specific images
|
Callbacks
Plugins are event driven. If a callback is not used by a plugin then it should not be present in the plugin.py file, Domoticz will not attempt to call callbacks that are not defined.
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: Connection, Status, Description
Called when connection to remote device either succeeds or fails, or when a connection is made to a listening Address:Port. Connection is the Domoticz Connection object associated with the event. Zero Status indicates success.
If Status is not zero then the Description will describe the failure. |
| onMessage | Parameters: Connection, Data.
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. |
| 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 | Legacy Plugin Framework ('import Domoticz')
Extended Plugin Framework ('import DomoticzEx')
ColorMode {
ColorModeNone = 0, // Illegal
ColorModeWhite = 1, // White. Valid fields: none
ColorModeTemp = 2, // White with color temperature. Valid fields: t
ColorModeRGB = 3, // Color. Valid fields: r, g, b.
ColorModeCustom = 4, // Custom (color + white). Valid fields: r, g, b, cw, ww, depending on device capabilities
ColorModeLast = ColorModeCustom,
};
Color {
ColorMode m;
uint8_t t; // Range:0..255, Color temperature (warm / cold ratio, 0 is coldest, 255 is warmest)
uint8_t r; // Range:0..255, Red level
uint8_t g; // Range:0..255, Green level
uint8_t b; // Range:0..255, Blue level
uint8_t cw; // Range:0..255, Cold white level
uint8_t ww; // Range:0..255, Warm white level (also used as level for monochrome white)
}
|
| onHeartbeat | Called every 'heartbeat' seconds (default 10) regardless of connection status.
Heartbeat interval can be modified by the Heartbeat command: Domoticz.Heartbeat(30)
Allows the Plugin to do periodic tasks including request reconnection if the connection has failed.
|
| onTimeout | Parameters: Connection
Called in response to a connection or read timeout.
|
| onDisconnect | Parameters: Connection
Called after the remote device is disconnected, Connection is the Domoticz Connection object associated with the event |
| onStop | Called when the hardware is stopped or deleted from Domoticz. |
| onDeviceAdded | Legacy Plugin Framework ('import Domoticz')
Extended Plugin Framework ('import DomoticzEx')
|
| onDeviceModified | Legacy Plugin Framework ('import Domoticz')
Extended Plugin Framework ('import DomoticzEx')
|
| onDeviceRemoved | Legacy Plugin Framework ('import Domoticz')
Extended Plugin Framework ('import DomoticzEx')
|
| onSecurityEvent | Legacy Plugin Framework ('import Domoticz')
Extended Plugin Framework ('import DomoticzEx')
|
| onWebSocketMessage | Parameters: Data (String).
Called when a frontend page sends a This callback is optional. If the plugin does not define it, inbound commands are silently dropped. An exception raised inside the callback is caught and logged by the framework; it does not crash Domoticz or block the WebSocket. Example:import json
class BasePlugin:
def onWebSocketMessage(self, Data):
try:
msg = json.loads(Data)
except ValueError:
msg = {"raw": Data}
Domoticz.Log("Received from frontend: " + str(msg))
# Respond to a ping command with a pong; echo everything else back
if isinstance(msg, dict) and msg.get("cmd") == "ping":
Domoticz.WebSocketSend({
"type": "pong",
"hwid": Parameters["HardwareID"],
"ts": msg.get("ts"),
})
else:
Domoticz.WebSocketSend({"echo": msg})
When the frontend sends See also: Plugin WebSocket Channel (UI section). |
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. All keyword arguments throughout the plugin framework are case-insensitive.
| Function | Description/Attributes | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Debug | Parameters: String
Write a message to Domoticz log only if verbose logging is turned on. | ||||||||||||||||||||||||||||||||||||||||
| Log | Parameters: String.
Write a message to Domoticz log | ||||||||||||||||||||||||||||||||||||||||
| Status | Parameters: String.
Write a status message to Domoticz log | ||||||||||||||||||||||||||||||||||||||||
| Error | Parameters: String
Write an error message to Domoticz log | ||||||||||||||||||||||||||||||||||||||||
| Debugging | Parameters: Integer.
Set logging level and type for debugging.
Mask values can be added together, for example to see debugging details around the plugin and its objects use: | ||||||||||||||||||||||||||||||||||||||||
| Heartbeat | Parameters: Integer (Optional).
Set the heartbeat interval in seconds if parameter supplied (initial value 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. Returns current Heartbeat value in seconds. | ||||||||||||||||||||||||||||||||||||||||
| Notifier | Parameters: Name, String.
Informs the plugin framework that the plugin's external hardware can consume Domoticz Notifications. | ||||||||||||||||||||||||||||||||||||||||
| SendNotification | Parameters: Subject (String, required), Body (String, optional), Priority (Integer, optional), Sound (String, optional), ExtraData (String, optional), SubSystem (String, optional), Delay (Integer, optional).
Sends a notification through Domoticz's built-in notification system. This allows plugins to send notifications directly without needing HTTP loopback requests. The notification is queued internally and delivered via the configured notification subsystems (e.g. Telegram, Pushover, FCM, etc.). All keyword arguments are case-insensitive.
# Send to all active notification subsystems
Domoticz.SendNotification(Subject="Alert", Body="Something happened")
# Send to a specific subsystem
Domoticz.SendNotification(Subject="Solar Update", Body="Production started", SubSystem="telegram")
# Send via email (requires email configured in Settings > Notifications)
Domoticz.SendNotification(Subject="Alert", Body="Temperature is too high!", SubSystem="email")
# With priority and delay
Domoticz.SendNotification(Subject="Warning", Body="Battery low", Priority=1, Delay=5)
Available in both the legacy | ||||||||||||||||||||||||||||||||||||||||
| Trace | Parameters: N/A, Boolean. Default: False.
When True, Domoticz will log line numbers of the lines being executed by the plugin. Calling Trace again with False will suppress line level logging. Usage: def onHeartBeat():
Domoticz.Trace(True)
Domoticz.Log("onHeartBeat called")
...
Domoticz.Trace(False)
| ||||||||||||||||||||||||||||||||||||||||
| Configuration | Parameters: Dictionary (Optional).
Returns a dictionary containing the plugin's configuration data that was previously stored. If a Dictionary paramater is supplied the database will be updated with the new configuration data. # Configuration Helpers
def getConfigItem(Key=None, Default={}):
Value = Default
try:
Config = Domoticz.Configuration()
if (Key != None):
Value = Config[Key] # only return requested key if there was one
else:
Value = Config # return the whole configuration if no key
except KeyError:
Value = Default
except Exception as inst:
Domoticz.Error("Domoticz.Configuration read failed: '"+str(inst)+"'")
return Value
def setConfigItem(Key=None, Value=None):
Config = {}
try:
Config = Domoticz.Configuration()
if (Key != None):
Config[Key] = Value
else:
Config = Value # set whole configuration if no key specified
Config = Domoticz.Configuration(Config)
except Exception as inst:
Domoticz.Error("Domoticz.Configuration operation failed: '"+str(inst)+"'")
return Config
| ||||||||||||||||||||||||||||||||||||||||
| Register | Parameters: Device (Class Object), Optional Unit (Class Object)
Must be positioned outside of any class code so that the Plugin Framework can process it during the module import. Changes the object type that Domoticz uses to represent Devices (and Units where applicable) to a user defined type. The specified class must inherit (directly or indirectly) from the default class type and the underlying object initialisation should be invoked (via super().__init__) to ensure there are no unexpected behaviours.
import Domoticz
from Domoticz import Device
class myDevice(Domoticz.Device):
def __init__(self, Name, Unit, TypeName="", Type=0, Subtype=0, Switchtype=0, Image=0, Options="", Used=0, DeviceID="", Description=""):
super().__init__(Name=Name, Unit=Unit, TypeName=TypeName, Type=Type, Subtype=Subtype, Switchtype=Switchtype, Image=Image, Options=Options, Used=Used, DeviceID=DeviceID, Description=Description)
# This code will run prior to onStart for existing data
self.localVariable = "Hello"
def localFunction(self):
Domoticz.Log("Function called")
# Callbacks cannot be overridden
Domoticz.Register(Device=myDevice)
import DomoticzEx as Domoticz
from DomoticzEx import Device, Unit
class myUnit(Domoticz.Unit):
def __init__(self, Name, DeviceID, Unit, TypeName="", Type=0, Subtype=0, Switchtype=0, Image=0, Options="", Used=0, Description=""):
super().__init__(Name, DeviceID, Unit, TypeName, Type, Subtype, Switchtype, Image, Options, Used, Description)
# NOTE: This code will run prior to onStart for existing data
self.Refresh() # Only DeviceID and Unit will be populated this early in the initialisation process so force a refresh
if (self.SubType == 73) and (self.SwitchType == 0):
self.__class__ = myOtherUnit # Run time polymorphism Python style
self.myVar = 0
# Optionally override onDeviceAdded, onDeviceModified, onDeviceRemoved or onCommand callbacks
def onDeviceModified(self):
Domoticz.Log("Unit onDeviceModified")
def onCommand(self, Command, Level, Hue):
Domoticz.Log("myUnit onCommand")
class myOtherUnit(myUnit):
# onCommand will be called directly from Domoticz
def onCommand(self, Command, Level, Hue):
Domoticz.Log("myOtherUnit onCommand")
class myDevice(Domoticz.Device):
def __init__(self, DeviceID):
super().__init__(DeviceID)
# This code will run prior to onStart for existing data
self.localVariable = "Hello"
def localFunction(self):
Domoticz.Log("Function called")
# Optionally override onDeviceAdded, onDeviceModified, onDeviceRemoved or onCommand callbacks
def onDeviceModified(self, Unit):
Domoticz.Log("Device onDeviceModified")
Domoticz.Register(Device=myDevice, Unit=myUnit)
| ||||||||||||||||||||||||||||||||||||||||
| WebSocketSend | Parameters: Payload (String or Dictionary, required).
Push a message to every frontend WebSocket client that is currently subscribed to
Integer values support the full 64-bit range (including JavaScript millisecond timestamps
such as
The call is non-blocking and returns immediately regardless of the number of connected clients.
The message is delivered only to clients that have previously called Each delivered WebSocket frame has the structure: {
"event": "plugin",
"plugin": "<key>",
"hwid": <HardwareID>,
"data": <payload>
}
where import Domoticz
class BasePlugin:
def onHeartbeat(self):
# Send a dict — automatically JSON-encoded
Domoticz.WebSocketSend({"counter": self._counter, "status": "ok"})
self._counter += 1
def onWebSocketMessage(self, Data):
# React to a command from the page
Domoticz.WebSocketSend({"ack": True})
Available in both the legacy | ||||||||||||||||||||||||||||||||||||||||
| Dump | Parameters: Object (Optional).
Dumps values to the Domoticz log. If an Object parameter is supplied, only that object's values are dumped; otherwise dumps context, local variables, and global variables. Useful during debugging or exception handling. Dumped values:
import DomoticzEx as Domoticz
from DomoticzEx import Device, Unit
import sys
import datetime
import json
modeCoolCmd = 'N000001{"SYST": {"OSS": {"MD": "C" } } }'
modeEvapCmd = 'N000001{"SYST": {"OSS": {"MD": "E" } } }'
modeHeatCmd = 'N000001{"SYST": {"OSS": {"MD": "H" } } }'
# Heater, Cooling and Evaporate common functionality
class hvacBase(Domoticz.Connection):
def __init__(self, DeviceID, Name, Image, Address, Port):
super().__init__(Name=Name, Transport="TCP/IP", Protocol="None", Address=Address, Port=Port)
self.isActive = False
self.DeviceID = DeviceID
self.DeviceImage = Image
self.Device = None
self.reportedMode = DeviceID
def onConnect(self, Connection, Status, Description):
if (self != Connection):
Domoticz.Error("Connection '"+Connection.Name+"' is not the same as '"+self.Name+"'")
if (Status == 0):
Domoticz.Log(self.Name+" connected successfully to: "+Connection.Address+":"+Connection.Port)
for device in Devices:
Devices[device].TimedOut = 0
else:
Domoticz.Log(self.Name+" failed to connect ("+str(Status)+") to: "+Connection.Address+":"+Connection.Port)
for device in Devices:
Devices[device].TimedOut = 1
class Heating(hvacBase):
def __init__(self, DeviceID, Name, Image, Address, Port):
super().__init__(DeviceID, Name, Image, Address, Port)
def onMessage(self, Connection, Response):
if (len(Response) > 10): # filter out 'HELLO' message
self.Disconnect()
jsonPayload = json.loads(Response[7:])
Domoticz.Dump()
2021-07-06 15:18:34.296 Rinnai: (Rinnai) Context dump:
2021-07-06 15:18:34.296 Rinnai: (Rinnai) ----> 'Address' '192.168.999.999'
2021-07-06 15:18:34.297 Rinnai: (Rinnai) ----> 'Baud' '-1'
2021-07-06 15:18:34.297 Rinnai: (Rinnai) ----> 'Device' 'DeviceID: 'HGOM', Units: 1'
2021-07-06 15:18:34.298 Rinnai: (Rinnai) ----> 'DeviceID' 'HGOM'
2021-07-06 15:18:34.298 Rinnai: (Rinnai) ----> 'DeviceImage' '15'
2021-07-06 15:18:34.298 Rinnai: (Rinnai) ----> 'Name' 'Heating'
2021-07-06 15:18:34.299 Rinnai: (Rinnai) ----> 'Parent' 'None'
2021-07-06 15:18:34.299 Rinnai: (Rinnai) ----> 'Port' '27847'
2021-07-06 15:18:34.299 Rinnai: (Rinnai) ----> 'Target' 'Name: 'Heating', Transport: 'TCP/IP', Protocol: 'None', Address: '192.168.999.999', Port: '27847', Baud: -1, Timeout: 0, Bytes: 1071, Connected: True, Last Seen: 2021-07-06 15:18:34, Parent: 'None''
2021-07-06 15:18:34.300 Rinnai: (Rinnai) ----> 'isActive' 'True'
2021-07-06 15:18:34.300 Rinnai: (Rinnai) ----> 'reportedMode' 'HGOM'
2021-07-06 15:18:34.300 Rinnai: (Rinnai) Locals dump:
2021-07-06 15:18:34.300 Rinnai: (Rinnai) ----> 'self' 'Name: 'Heating', Transport: 'TCP/IP', Protocol: 'None', Address: '192.168.999.999', Port: '27847', Baud: -1, Timeout: 0, Bytes: 1071, Connected: True, Last Seen: 2021-07-06 15:18:34, Parent: 'None''
2021-07-06 15:18:34.301 Rinnai: (Rinnai) ----> 'Connection' 'Name: 'Heating', Transport: 'TCP/IP', Protocol: 'None', Address: '192.168.999.999', Port: '27847', Baud: -1, Timeout: 0, Bytes: 1071, Connected: True, Last Seen: 2021-07-06 15:18:34, Parent: 'None''
2021-07-06 15:18:34.301 Rinnai: (Rinnai) ----> 'Response' 'b'N000000[{"SYST": {"CFG": {"MTSP": ... "MT": "231" } } }]''
2021-07-06 15:18:34.302 Rinnai: (Rinnai) ----> 'jsonPayload' '[{'SYST': {'CFG': {'MTSP': ... 'MT': '231'}}}]'
2021-07-06 15:18:34.303 Rinnai: (Rinnai) Globals dump:
2021-07-06 15:18:34.304 Rinnai: (Rinnai) ----> 'Domoticz' '<module 'DomoticzEx' (built-in)>'
2021-07-06 15:18:34.304 Rinnai: (Rinnai) ----> 'sys' '<module 'sys' (built-in)>'
2021-07-06 15:18:34.304 Rinnai: (Rinnai) ----> 'datetime' '<module 'datetime' from 'C:\\Program Files (x86)\\Python39-32\\Lib\\datetime.py'>'
2021-07-06 15:18:34.304 Rinnai: (Rinnai) ----> 'json' '<module 'json' from 'C:\\Program Files (x86)\\Python39-32\\Lib\\json\\__init__.py'>'
2021-07-06 15:18:34.305 Rinnai: (Rinnai) ----> 'modeCoolCmd' 'N000001{"SYST": {"OSS": {"MD": "C" } } }'
2021-07-06 15:18:34.305 Rinnai: (Rinnai) ----> 'modeEvapCmd' 'N000001{"SYST": {"OSS": {"MD": "E" } } }'
2021-07-06 15:18:34.306 Rinnai: (Rinnai) ----> 'modeHeatCmd' 'N000001{"SYST": {"OSS": {"MD": "H" } } }'
2021-07-06 15:18:34.306 Rinnai: (Rinnai) ----> '_plugin' '<plugin.BasePlugin object at 0x008544E0>'
2021-07-06 15:18:34.306 Rinnai: (Rinnai) ----> 'Parameters' '{'HardwareID': 2, 'HomeFolder': 'plugins\\Domoticz-RinnaiTouch-Plugin\\', 'StartupFolder': '', 'UserDataFolder': '', 'WebRoot': '', 'Database': 'domoticz.db', 'Language': 'en', 'Version': '1.0.0', 'Author': 'dnpwwo', 'Name': 'Rinnai', 'Address': '', 'Port': '27847', 'SerialPort': '', 'Username': '', 'Password': '', 'Key': 'RinnaiTouch', 'Mode1': '', 'Mode2': '', 'Mode3': '', 'Mode4': '', 'Mode5': 'True', 'Mode6': '0', 'DomoticzVersion': '2021.1 (build 2107)', 'DomoticzHash': '2107', 'DomoticzBuildTime': '1970-01-01 11:35:07'}'
2021-07-06 15:18:34.307 Rinnai: (Rinnai) ----> 'Devices' '{'HGOM': <plugin.hvacDevice object at 0x008574E0>, 'SYST': <plugin.hvacDevice object at 0x00857570>}'
2021-07-06 15:18:34.307 Rinnai: (Rinnai) ----> 'Images' '{}'
2021-07-06 15:18:34.308 Rinnai: (Rinnai) ----> 'Settings' '{'DB_Version': '148', 'Title': 'Domoticz', 'LightHistoryDays': '30', ... 'MaxElectricPower': '6000'}'
|
Protocol Details
| Protocol | Details | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| HTTP/HTTPS | HTTP and HTTPS are handled the same from a protocol perspective. HTTPS signals the underlying transport to use SSL/TLS, see Connections.
The HTTP protocol handles incoming and outgoing messages for both client connections requesting content from remote websites and server side listeners waiting incoming requests. Data is passed to the protocol (Connection.Send()) and passed back to the plugin (onMessage) using dictionaries. Key values in these directories are described below with an emphasis on the impact they have to what Domoticz transmits.
| ||||||||||||||||||||||||||||
| WS/WSS | WebSockets (WS) and Secure WebSockets (WSS) are handled the same from a protocol perspective. WSS signals the underlying transport to use SSL/TLS.
WebSocket connections are initially made over HTTP and then 'upgraded' to be Web Sockets. This means that the message parameters (and matching response) use the protocol details for HTTP until the Server switches to the WebSocket protocol. Successful upgrades to the WebSocket protocol are signalled by an HTTP response with a "Status" of "101".
def onConnect(self, Connection, Status, Description):
if Status == 0:
Domoticz.Log("Connected successfully to: " + Connection.Address + ":" + Connection.Port)
# End point is now connected so request the upgrade to Web Sockets
send_data = {'URL': Parameters["Mode1"],
'Headers': {'Host': Parameters["Address"],
'Origin': 'https://' + Parameters["Address"],
'Sec-WebSocket-Key': base64.b64encode(secrets.token_bytes(16)).decode("utf-8")}}
Connection.Send(send_data)
|
UI
There is no built-in capabilities to create UI for plugin but you can use workaround with Domoticz Custom Page
Example of complex UI implementation you can find in this plugin
Setup custom page within plugin
- Create custom HTML file in your plugins' folder, let's assume it is index.html
- General idea is to install custom page when your plugin starts and remove it when plugin stops. To do so you need to modify your plugin's onStart and onStop methods. Base folder for Python will be Domoticz root (not your plugin root)
... from shutil import copy2 import os ... class Plugin: def onStart(self): ... copy2('./plugins/myplugin/index.html', './www/templates/myplugin.html') def onStop(self): ... if (os.path.exists('./www/templates/myplugin.html')): os.remove('./www/templates/myplugin.html')
Connect your page to Domoticz UI core
Domoticz uses AngularJS framework for its UI. Additionally it uses requirejs library for lazy loading. It is not required to follow the same patterns in your custom pages but following them will allow to access built-in classes and components. Every custom page would be loaded and rendered as template for AngularJS framework with access to $rootScope and other global variables.
Example of custom page with access to AngularJS and domoticzApi service:
<!-- Placeholder for page content -->
<div id="plugin-view"></div>
<!-- Template for custom component -->
<script type="text/ng-template" id="app/myplugin/sampleComponent.html">
<div class="container">
<a class="btnstylerev" back-button>{{ ::'Back' | translate }}</a>
<h2 class="page-header">My Plugin UI</h2>
<p>{{ $ctrl.message }}</p>
</div>
</script>
<script>
require(['app'], function(app) {
// Custom component definition
app.component('myPluginRoot', {
templateUrl: 'app/myplugin/sampleComponent.html',
controller: function($scope, domoticzApi) {
var $ctrl = this;
$ctrl.message = 'This is my plugin'
}
});
// This piece triggers Angular to re-render page with custom components support
angular.element(document).injector().invoke(function($compile) {
var $div = angular.element('<my-plugin-root />');
angular.element('#plugin-view').append($div);
var scope = angular.element($div).scope();
$compile($div)(scope);
});
});
</script>
Plugin WebSocket Channel
Plugins can open a real-time bidirectional channel between the C++ plugin process and any browser page that subscribes to it. No HTTP polling is needed; messages are pushed over the same persistent WebSocket connection that Domoticz already uses for device updates.
Channel key and multi-instance behaviour
The channel is identified by the plugin key: the key attribute in the plugin's XML
<plugin> tag. This is the same value used in Parameters["Key"] inside the plugin.
Important — key must match exactly.
The string passed to
livesocket.subscribePlugin(),livesocket.sendPluginCommand(), and the topic suffix in{"event":"subscribe","topic":"plugin:<key>"}must be the exact value of the XMLkeyattribute (also visible asCPlugin::m_PluginKeyin C++ and in the"plugin"field of everyplugin_command_ackframe). A mismatch results in silently receiving nothing — the server logs a status line when a
plugin_commandtargets a key with no running instances.
Example — plugin XML and the matching frontend call:
# plugin.py — the key is "MyPlugin"
"""
<plugin key="MyPlugin" name="My Plugin" author="me" version="1.0.0">
...
</plugin>
"""
// custom page — must use the identical string "MyPlugin"
livesocket.subscribePlugin('MyPlugin');
livesocket.sendPluginCommand('MyPlugin', { cmd: 'toggle' });
When more than one hardware entry uses the same plugin (i.e. the same key), all
running instances share the channel:
- Outbound (plugin → page): A message sent by any instance is delivered to every subscribed
page, tagged with the originating hwid (HardwareID integer) so the page can tell
instances apart.
- Inbound (page → plugin): A
plugin_commandframe fans out to every running
instance unless the page supplies a specific hwid to target one instance only.
Plugin-side API
- Push a message from Python to subscribed pages
Domoticz.WebSocketSend(payload)— payload may be adict(auto-JSON) orstr.
- Receive a message from a page
- Implement
onWebSocketMessage(self, Data).Datais always astr;
call json.loads(Data) if the page sent a JSON object.
See C++ Callable API (WebSocketSend) and Callbacks (onWebSocketMessage) for full parameter documentation.
Frontend custom-page snippet
Place this in your plugin's custom HTML template (shipped in the plugin folder, copied to
www/templates/ by onStart). The snippet follows the same AngularJS
controller idiom used by Domoticz's own templates (e.g. www/templates/Forecast.html):
- The root
<div>carriesng-app="domoticz" ng-controller="myCtrl". - A temporary module is declared with
angular.module(['myApp'], []). - The controller receives
livesocketvia the standard DI array. - All view-bound data lives on
$scope.*; the template usesng-repeat/ng-click— no directgetElementById/textContentwrites. - Cleanup is done in
$scope.$on('$destroy', ...).
<style>
.myplugin-page { padding: 8px 4px; color: var(--dz-text, #ddd); }
.myplugin-table-wrap {
max-height: 320px; overflow-y: auto;
border-radius: var(--dz-border-radius, 8px);
background: var(--dz-panel-bg, #1e1e1e); margin-top: 8px;
}
.myplugin-table { width: 100%; border-collapse: collapse; font-size: 0.88em; font-family: monospace; }
.myplugin-table th {
position: sticky; top: 0; background: var(--dz-panel-bg, #1e1e1e);
color: var(--dz-text-muted, #9ab); font-weight: normal;
text-align: left; padding: 6px 10px;
border-bottom: 1px solid rgba(255,255,255,0.08);
}
.myplugin-table td { padding: 4px 10px; border-bottom: 1px solid rgba(255,255,255,0.04); word-break: break-all; }
.myplugin-time { white-space: nowrap; color: var(--dz-text-muted, #9ab); }
.myplugin-empty { text-align: center; padding: 24px; color: var(--dz-text-muted, #9ab); opacity: 0.6; }
.myplugin-status { color: var(--dz-text-muted, #9ab); font-size: 0.85em; margin-left: 8px; }
</style>
<div ng-app="domoticz" ng-controller="myCtrl" class="myplugin-page">
<a class="btnstylerev" back-button>{{ ::'Back' | translate }}</a>
<h2 class="page-header">My Plugin Live Data</h2>
<div style="margin:8px 0;display:flex;align-items:center;gap:8px;">
<button class="btnstyle3" ng-click="sendCommand()">{{ ::'Send command' | translate }}</button>
<span class="myplugin-status" ng-if="lastSentAt">{{ ::'Sent' | translate }}: {{lastSentAt}}</span>
</div>
<div class="myplugin-table-wrap" id="myplugin-scroll">
<table class="myplugin-table">
<thead>
<tr>
<th class="myplugin-time">{{ ::'Time' | translate }}</th>
<th>{{ ::'HW' | translate }}</th>
<th>{{ ::'Data' | translate }}</th>
</tr>
</thead>
<tbody>
<tr ng-if="!messages.length">
<td colspan="3" class="myplugin-empty">{{ ::'Waiting for messages…' | translate }}</td>
</tr>
<tr ng-repeat="m in messages track by m.seq">
<td class="myplugin-time">{{m.time}}</td>
<td>{{m.hwid}}</td>
<td>{{m.display}}</td>
</tr>
</tbody>
</table>
</div>
</div>
<script>
var app = angular.module(['myApp'], []);
app.controller('myCtrl', function ($scope, $timeout, livesocket) {
var PLUGIN = 'MyPlugin'; /* must equal the XML <plugin key="..."> value */
var seq = 0;
$scope.messages = []; /* [{seq, time, hwid, display}] — bound to ng-repeat */
$scope.lastSentAt = '';
function formatData(data) {
/* Render both JSON objects and plain strings readably */
if (data === null || data === undefined) { return String(data); }
if (typeof data === 'string') { return data; }
try { return JSON.stringify(data); } catch (e) { return String(data); }
}
/* Subscribe — auto-restored on WebSocket reconnect */
livesocket.subscribePlugin(PLUGIN);
/* Receive pushed messages from any running instance */
var offMessage = livesocket.onPluginMessage(PLUGIN, function (msg) {
/* msg = { plugin: 'MyPlugin', hwid: <int>, data: <any> } */
seq++;
var now = new Date(), pad = function (n) { return n < 10 ? '0' + n : n; };
$scope.messages.push({
seq: seq,
time: pad(now.getHours()) + ':' + pad(now.getMinutes()) + ':' + pad(now.getSeconds()),
hwid: msg.hwid,
display: formatData(msg.data)
});
/* Scroll the table to the latest row after ng-repeat renders it */
$timeout(function () {
var el = document.getElementById('myplugin-scroll');
if (el) { el.scrollTop = el.scrollHeight; }
}, 0);
});
/* Send a command to all running instances */
$scope.sendCommand = function () {
livesocket.sendPluginCommand(PLUGIN, { cmd: 'toggle' });
$scope.lastSentAt = new Date().toLocaleTimeString();
};
/* Tear down on page leave */
$scope.$on('$destroy', function () {
if (offMessage) { offMessage(); offMessage = null; }
livesocket.unsubscribePlugin(PLUGIN);
});
});
</script>
Security
Only WebSocket sessions with at least URIGHTS_SWITCHER rights (the standard switch-permission
level) may send inbound plugin_command frames. Unauthenticated or viewer-only sessions
receive a Forbidden error in the ack frame. Payloads larger than 64 KiB are rejected
before they reach the plugin queue.
Crash and lifecycle safety
The channel registration is handled automatically by the plugin framework:
- The instance is registered when the plugin starts and unregistered when it stops or is deleted,
even if the plugin has crashed (the framework's own cleanup path removes the entry).
- A stale registration from a previous run is silently replaced on the next start.
- All inbound delivery is non-blocking: the WebSocket thread enqueues the message on the plugin's
own work queue and returns immediately, so a slow or stuck plugin never stalls the web server.
Example
A runnable demonstration is shipped under plugins/examples/WebSocketChannelTest/
in the Domoticz source tree. Domoticz scans plugins/<Name>/plugin.py
directly and does not scan plugins/examples/, so to use it you must copy
the folder to plugins/WebSocketChannelTest/ (or any plugins/<Name>/)
and restart Domoticz. The plugin depth matches plugins/Domoticz-MeshCore-Plugin,
so domoticz_root is computed with exactly 2× .. as MeshCore does.
It shows a counter pushed from the plugin on every heartbeat, a handler for inbound commands, and
the matching HTML custom page. The custom page (wschanneltest.html) follows the same
AngularJS controller idiom as www/templates/Forecast.html: ng-app/ng-controller
on the root element, $scope-bound data, ng-repeat for the message list, and
$scope.$on('$destroy', ...) for teardown — making it a reference example for
template authors.
Testing
An automated end-to-end test script is provided at test/python/test_plugin_websocket.py.
It connects to a running Domoticz instance over its native WebSocket endpoint, drives the full
outbound/inbound/fan-out/hwid-targeting/topic-filtering flow and reports PASS/FAIL per check.
The round-trip checks (B and C) send {"cmd":"ping","ts":<ms>} and verify that the
plugin replies with {"type":"pong","tag":...,"hwid":...,"ts":<ms>} — asserting both
correct delivery count and that the ts value is echoed back unchanged.
Prerequisites: the example is tracked at plugins/examples/WebSocketChannelTest/;
copy that folder to plugins/WebSocketChannelTest/ (Domoticz does not scan
plugins/examples/ directly). Add it twice as hardware (Tag A and Tag B)
and have Domoticz running, then:
pip install websocket-client
python test/python/test_plugin_websocket.py [http://host:port]
Environment variables DOMOTICZ_URL, DOMOTICZ_USER, DOMOTICZ_PASS
override the defaults (http://127.0.0.1:8080, admin, domoticz).
For a manual checklist see the step-by-step procedure in
Plans/plugin-websocket-channel/F7-test-harness.md.
Examples
There are a number of examples that are available that show potential usages and patterns that may be useful that can be found on Github:
| Example | Description |
|---|---|
| Base Template | A good starting point for developing a plugin. |
| Denon/Marantz | Support for Denon AVR amplifiers.
|
| DLink DSP-W215 | TCP/IP connectivity using HTTP protocol.
|
| HTTP | Shows how to do HTTP connections and handle redirects. |
| HTTP Listener | Shows how to listen for incoming connections.
|
| Kodi | Alternate Kodi plugin to the built in one.
|
| RAVEn | Alternate RAVEn Energy Monitor plugin to the built in one.
|
| UDP Discovery | Logs incoming UDP Broadcast messages for either Dynamic Device Discovery (DDD) or Simple Service Discovery Protocol (SSDP) protocols
|
| Mutli-Threaded | Starts a Queue on a thread to write log messages and shuts down properly |
| Web Socket Client | Establishes a Web Socket connection and shows how to send and receive messages |
| WebSocketChannelTest | Demonstrates the bidirectional Plugin WebSocket Channel.
payload receives a
|
Troubleshooting
Importing Modules Fails
Python 'import' that are unsuccessful will stop the entire plugin from importing and running unless they are wrapped within a 'try'/'except' pair. If a module is not found the Plugin Framework will provide as much information as it gets from Python. For example, an attempt to import fakemodule in a plugin called 'Google Devices' will be reported like this in the Domoticz log:
2019-03-23 17:17:44.483 Error: (GoogleDevs) failed to load 'plugin.py', Python Path used was '/home/domoticz/plugins/Domoticz-Google-Plugin/:/usr/lib/python36.zip:/usr/lib/python3.6:/usr/lib/python3.6:/usr/lib/python3.6/lib-dynload:/usr/local/lib/python3.6/dist-packages:/usr/lib/python3/dist-packages:/usr/lib/python3.6/dist-packages'. 2019-03-23 17:17:44.483 Error: (Google Devices) Module Import failed, exception: 'ModuleNotFoundError' 2019-03-23 17:17:44.483 Error: (Google Devices) Module Import failed: ' Name: fakemodule' 2019-03-23 17:17:44.483 Error: (Google Devices) Error Line details not available.
the framework shows the directories it searches. Broadly these are: the plugin directory, the existing Python path as picked up when Domoticz started and the 'site' directories for system level modules installed with pip3. Local 'site' directories are not searched by defaul although plugin developers can add any directory the like to the path inside the plugin. Pip3 installs packages in system directories when sudo is used on linux platforms.
Plugin Crashes Domoticz
Yes, this is unfortunately possible. Python3 itself is vulnerable to segmentation faults and Domoticz just inherits this. If you don't believe me try typing python3 -c "import ctypes; ctypes.string_at(0)" at a linux command line with Python installed.
To help troubleshoot these issues the Plugin Framework attempts to load the standard 'faulthandler' module for each plugin just before the plugin itself is loaded. If the plugin triggers any signals a thread by thread stack trace will be printed. To see this you will need to run Domoticz from the command line or redirect sys.stderr to a file (how would I do that?)
Output for a multi-threaded plugin will look something like this, a Python stack trace followed by a normal Domoticz one. The offending line in the plugin is shown in bold:
Fatal Python error: Segmentation fault
Thread 0xa8d0eb40 (most recent call first):
File "/usr/local/lib/python3.6/dist-packages/pychromecast/socket_client.py", line 361 in run_once
File "/usr/local/lib/python3.6/dist-packages/pychromecast/socket_client.py", line 341 in run
File "/usr/lib/python3.6/threading.py", line 916 in _bootstrap_inner
File "/usr/lib/python3.6/threading.py", line 884 in _bootstrap
Current thread 0xab553b40 (most recent call first):
....
File "<frozen importlib._bootstrap>", line 219 in _call_with_frames_removed
File "<frozen importlib._bootstrap_external>", line 678 in exec_module
File "<frozen importlib._bootstrap>", line 665 in _load_unlocked
File "<frozen importlib._bootstrap>", line 955 in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 971 in _find_and_load
File "/home/domoticz/plugins/Domoticz-Google-Plugin/plugin.py", line 322 in handleMessage
File "/usr/lib/python3.6/threading.py", line 864 in run
File "/usr/lib/python3.6/threading.py", line 916 in _bootstrap_inner
File "/usr/lib/python3.6/threading.py", line 884 in _bootstrap
Thread 0xad597b40 (most recent call first):
File "/usr/lib/python3.6/threading.py", line 299 in wait
File "/usr/local/lib/python3.6/dist-packages/zeroconf.py", line 1794 in wait
File "/usr/local/lib/python3.6/dist-packages/zeroconf.py", line 1365 in run
File "/usr/lib/python3.6/threading.py", line 916 in _bootstrap_inner
File "/usr/lib/python3.6/threading.py", line 884 in _bootstrap
Thread 0xb57cfb40 (most recent call first):
2019-03-23 11:21:04.003 Error: Domoticz(pid:24259, tid:24500('PluginMgr')) received fatal signal 11 (Segmentation fault)
2019-03-23 11:21:04.003 Error: siginfo address=0x5ec3, address=0xb7f51d09
2019-03-23 11:21:04.741 Error: Thread 19 (Thread 0xab553b40 (LWP 24500)):
2019-03-23 11:21:04.741 Error: #0 0xb7f51d09 in __kernel_vsyscall ()
...
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 on Windows.
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). Alternatively, if pip3 is installed (sudo apt install python3-pip if on Raspian or a Debian distro) then install the debugger using sudo pip3 install rpdb
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':
pi@bob:~$ 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