You can find an empty plugin database here
Here is its structure: first, a folder named after your plugin (more precisely, its unique identifier), which must contain the following subfolders:
3rdparty : Folder containing the external libraries used by the plugin (for example, for the SMS plugin, a library for serial communication in PHP).core : Folder containing all internal operating files.
class : Folder containing the plugin class.php : A file that may contain functions that do not necessarily belong to a class (often used to allow the inclusion of multiple classes or configuration files at once).config : Plugin configuration file.ajax : Folder containing the target files for AJAX calls.i18n : Folder containing the plugin’s .json translation files.template : Folder containing the HTML templates for tiles specific to the plugin’s devices, located in the “dashboard” and “mobile” subfolders.desktop : Folder containing the plugin’s “desktop” view (as opposed to the “mobile” view).
js : Folder containing all JavaScript files of the type “plugin interface” for the plugin.php : Folder containing all PHP files of the type “plugin interface” for the plugin.css : If necessary, all CSS files for the plugin, including any fonts.modal : Folder containing the plugin’s modal code.img : Folder for the images (PNG, JPG, etc.) required by the plugin.plugin_info : Contains the files that allow Jeedom to identify the plugin, install it, and configure it.
info.json : A file containing basic information about the plugin. This file is required; otherwise, Jeedom will not recognize the plugin. It includes, among other things, the module ID, description, and installation instructions…install.php : File containing (if necessary) instructions for installing and uninstalling the plugin.configuration.php : File containing the plugin’s configuration settings that are independent of its devices (for example, for the Z-Wave module, the IP address of the Raspberry Pi with the Razberry board)docs : Must contain the plugin’s documentation in Markdown format, including the root directory and the index.md file. All images are located in docs/images. The documentation itself is in a folder specific to the language (e.g., in French: docs/fr\_FR)ressources : Directory for potential daemons and dependencies.data : Folder used for files generated by the user’s Jeedom-specific plugin.As for the file naming convention, here are the Requirements:
.class.phpnom\_class.class.php.inc.php.config.phpHere are the recommendations:
.ajax.phpinfo.jsonSee here
install.phpFile containing installation instructions for a plugin:
It is composed as follows:
The first commented section contains the license (which is better). The one used here states that the file belongs to Jeedom and that it is open source. Next comes the inclusion of the Jeedom core (this provides access to internal functions). Then come the three functions:
pluginid_install() : a method for installing the plugin. In this case, the installation adds a cron job to Jeedompluginid_update() : a method for installing the plugin. Used here to restart the cron jobpluginid_remove() : a method for removing the plugin. Here, the function removes the Jeedom cron job during uninstallationExample:
<?php
/* This file is part of Jeedom.
*
* Jeedom is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Jeedom is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Jeedom. If not, see <http://www.gnu.org/licenses/>.
*/
require_once dirname(__FILE__) . '/../../../core/php/core.inc.php';
function openzwave_install() {
$cron = cron::byClassAndFunction('zwave', 'pull');
if (!is_object($cron)) {
$cron = new cron();
$cron->setClass('zwave');
$cron->setFunction('pull');
$cron->setEnable(1);
$cron->setDeamon(1);
$cron->setSchedule('* * * * *');
$cron->save();
}
}
function openzwave_update() {
$cron = cron::byClassAndFunction('zwave', 'pull');
if (!is_object($cron)) {
$cron = new cron();
$cron->setClass('zwave');
$cron->setFunction('pull');
$cron->setEnable(1);
$cron->setDeamon(1);
$cron->setSchedule('* * * * *');
$cron->save();
}
$cron->stop();
}
function openzwave_remove() {
$cron = cron::byClassAndFunction('zwave', 'pull');
if (is_object($cron)) {
$cron->remove();
}
}
?>
configuration.phpFile used to request configuration information from the user:
The file consists of:
Next comes the requested parameter (there may be several); this is standard Bootstrap syntax for forms. The only specific requirements are the class (configKey) on the parameter element, as well as the “data-l1key” attribute, which specifies the parameter’s name. To retrieve the parameter’s value elsewhere in the plugin, simply do the following: config::byKey(NOM_PARAMETRE, PLUGIN_ID)
Example:
<?php
/* This file is part of Jeedom.
*
* Jeedom is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Jeedom is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Jeedom. If not, see <http://www.gnu.org/licenses/>.
*/
require_once dirname(__FILE__) . '/../../../core/php/core.inc.php';
include_file('core', 'authentification', 'php');
if (!isConnect()) {
include_file('desktop', '404', 'php');
die();
}
?>
<form class="form-horizontal">
<fieldset>
<div class="form-group">
<label class="col-lg-2 control-label">Zway IP</label>
<div class="col-lg-2">
<input class="configKey form-control" data-l1key="zwaveAddr" />
</div>
</div>
<div class="form-group">
<label class="col-lg-4 control-label">Supprimer automatiquement les périphériques exclus</label>
<div class="col-lg-4">
<input type="checkbox" class="configKey" data-l1key="autoRemoveExcludeDevice" />
</div>
</div>
<div class="form-group">
<label class="col-lg-4 control-label">J'utilise un serveur openzwave</label>
<div class="col-lg-4">
<input type="checkbox" class="configKey" data-l1key="isOpenZwave" />
</div>
</div>
</fieldset>
</form>
This folder contains the actual view. It must include the plugin’s configuration page (the one that appears when the user goes to Plugins → Categories → your plugin). We recommend naming this page after your plugin’s ID. It may also contain the dashboard (the page the user will find under Home → your plugin’s name).
All files in this folder must end with .php and must begin with:
<?php
if (!isConnect('admin')) {
throw new Exception('{{401 - Accès non autorisé}}');
}
sendVarToJS('eqType', 'mail');
?>
Once you’re on this page, you’ll have PHP access to all of Jeedom’s core functions (see here ) as well as those of all installed modules, including yours.
Since all of these pages are views, they primarily use HTML syntax. For everything related to presentation, Jeedom relies mainly on Bootstrap, so all of the documentation is applicable.
To simplify plugin creation, you can include the JavaScript template script for plugins in your page:
<?php include_file('core', 'plugin.template', 'js'); ?>
Place this at the very bottom of your page; it is only useful on your plugin’s configuration page. This script reduces the required JavaScript to a single function (see the section on JS files).
On your configuration page, HTML syntax has been implemented to make your life easier. So for most plugins, all you’ll need to do is write HTML to store your information in the database and then reuse it in your class.
The syntax is quite simple: your element (input, select, etc.) must have the CSS class eqLogicAttr (or cmdAttr for commands) and an attribute specifying the property name:
<input type="text" class="eqLogicAttr form-control" data-l1key="name" placeholder="{{Nom de l'équipement mail}}"/>
For example, when loading data, Jeedom will populate the input field with the device name, and when saving, it will retrieve that value and store it back in the database. Here’s a quick tip: some properties are actually JSON strings in the database (which gives the plugin quite a bit of flexibility); in that case, all you need to do is:
<input class="eqLogicAttr form-control" data-l1key='configuration' data-l2key='fromName' />
For a list of device and command properties, click here (to see which properties are JSON, just look at the getter or setter; if it takes two parameters, then it’s JSON)
One last important point about the configuration page: it can contain as many devices and commands as needed. However, there are a few rules to follow:
All elements with the eqLogicAttr class must be contained within an element with the css eqLogic class. The same applies to elements with the css cmdAttr class, which must be contained within an element with the cmd class. All commands for a device must be contained within the element with the corresponding eqLogic class.
All JS files must be located in the JS folder (easy!!!). We recommend naming it after your plugin’s ID (in the configuration section; for the panel, you can name it whatever you like). This JS file (the one for the plugin’s configuration) must contain at least one method, addCmdToTable, which takes the command object to be added as a parameter. Here’s a simple example:
function addCmdToTable(_cmd) {
if (!isset(_cmd)) {
var _cmd = {configuration: {}};
}
var tr = ''; tr += '';
tr += '<input class="cmdAttr form-control input-sm" data-l1key="id" style="display : none;">';
tr += '<input class="cmdAttr form-control input-sm" data-l1key="name">'; tr += '<input class="cmdAttr form-control input-sm" data-l1key="configuration" data-l2key="recipient">'; tr += '';
tr += '<input class="cmdAttr form-control input-sm" data-l1key="type" value="action" style="display : none;">';
tr += '<input class="cmdAttr form-control input-sm" data-l1key="subType" value="message" style="display : none;">';
if (is_numeric(_cmd.id)) {
tr += '<a class="btn btn-default btn-xs cmdAction" data-action="test"><i class="fa fa-rss"></i> </a>';
}
tr += '<i class="fa fa-minus-circle pull-right cmdAction cursor" data-action="remove"></i></td>';
tr += '';
$('#table_cmd tbody').append(tr);
$('#table_cmd tbody tr:last').setValues(_cmd, '.cmdAttr');
}
You’ll notice that there is one line per command and that each line has the CSS class cmd. You can also see the elements that have the class cmdAttr.
Several important points:
One last point: a more comprehensive example using command types and subtypes:
function addCmdToTable(_cmd) {
if (!isset(_cmd)) {
var _cmd = {};
}
if (!isset(_cmd.configuration)) {
_cmd.configuration = {};
}
var selRequestType = '<select style="width : 90px;" class="cmdAttr form-control input-sm" data-l1key="configuration" data-l2key="requestType">';
selRequestType += '<option value="script">{{Script}}</option>';
selRequestType += '<option value="http">{{Http}}</option>';
selRequestType += '</select>';
var tr = ''; tr += '<input class="cmdAttr form-control input-sm" data-l1key="name" style="width : 140px;">';
tr += '<input class="cmdAttr form-control input-sm" data-l1key="id" style="display : none;">';
tr += '' + selRequestType;
tr += '<div class="requestTypeConfig" data-type="http">';
tr += '<input type="checkbox" class="cmdAttr" data-l1key="configuration" data-l2key="noSslCheck" />Ne pas vérifier SSL';
tr += '</div>';
tr += ''; tr += '';
tr += '<span class="type" type="' + init(_cmd.type) + '">' + jeedom.cmd.availableType() + '</span>';
tr += '<span class="subType" subType="' + init(_cmd.subType) + '"></span>';
tr += ''; tr += '<textarea style="height : 95px;" class="cmdAttr form-control input-sm" data-l1key="configuration" data-l2key="request"></textarea>';
tr += '<a class="btn btn-default browseScriptFile cursor input-sm" style="margin-top : 5px;"><i class="fa fa-folder-open"></i> {{Parcourir}}</a> ';
tr += '<a class="btn btn-default editScriptFile cursor input-sm" style="margin-top : 5px;"><i class="fa fa-edit"></i> {{Editer}}</a> ';
tr += '<a class="btn btn-success newScriptFile cursor input-sm" style="margin-top : 5px;"><i class="fa fa-file-o"></i> {{Nouveau}}</a> ';
tr += '<a class="btn btn-danger removeScriptFile cursor input-sm" style="margin-top : 5px;"><i class="fa fa-trash-o"></i> {{Supprimer}}</a> ';
tr += '<a class="btn btn-warning bt_shareOnMarket cursor input-sm" style="margin-top : 5px;"><i class="fa fa-cloud-upload"></i> {{Partager}}</a> ';
tr += '</div>';
tr += ''; tr += '';
tr += '<input class="cmdAttr form-control tooltips input-sm" data-l1key="unite" style="width : 100px;" placeholder="{{Unité}}" title="{{Unité}}">';
tr += '<input class="tooltips cmdAttr form-control input-sm" data-l1key="configuration" data-l2key="minValue" placeholder="{{Min}}" title="{{Min}}"> ';
tr += '<input class="tooltips cmdAttr form-control input-sm" data-l1key="configuration" data-l2key="maxValue" placeholder="{{Max}}" title="{{Max}}">';
tr += ''; tr += '';
tr += '<span><input type="checkbox" class="cmdAttr" data-l1key="isHistorized" /> {{Historiser}}<br/></span>';
tr += ''; tr += '';
if (is_numeric(_cmd.id)) {
tr += '<a class="btn btn-default btn-xs cmdAction" data-action="test"><i class="fa fa-rss"></i> {{Tester}}</a>';
}
tr += '<i class="fa fa-minus-circle pull-right cmdAction cursor" data-action="remove"></i></td>';
tr += '';
$('#table_cmd tbody').append(tr);
$('#table_cmd tbody tr:last').setValues(_cmd, '.cmdAttr');
if (isset(_cmd.configuration.requestType)) {
$('#table_cmd tbody tr:last .cmdAttr[data-l1key=configuration][data-l2key=requestType]').value(init(_cmd.configuration.requestType));
$('#table_cmd tbody tr:last .cmdAttr[data-l1key=configuration][data-l2key=requestType]').trigger('change');
}
if (isset(_cmd.type)) {
$('#table_cmd tbody tr:last .cmdAttr[data-l1key=type]').value(init(_cmd.type));
}
jeedom.cmd.changeType($('#table_cmd tbody tr:last'), init(_cmd.subType));
initTooltips();
}
Here, we can see:
jeedom.cmd.availableType() will insert a drop-down menu with a list of known types (actions and info for now)<span class="subType" subType="' + init(\_cmd.subType) + '"><\span> : where the subtype select should be placedjeedom.cmd.changeType(\$('\#table\_cmd tbody tr:last'), init(\_cmd.subType)) which allows you to initialize the subtype with the correct valueOther JavaScript functions can be used:
printEqLogic which takes the entire equipment object as a parameter (useful when processing data before returning it). It is called when displaying equipment datasaveEqLogic which takes the equipment object as a parameter to be saved to the database (useful if you need to perform some processing before saving) One last thing: for JS files, here’s how to include them cleanly on your PHP page:<?php include_file('desktop', 'weather', 'js', 'weather'); ?>
The first argument specifies the folder where it is located (note that this is the parent folder of the JS folder), the second is the name of your JavaScript file, the third tells Jeedom that it is a JS file, and the last specifies which plugin it is in.
This folder contains your CSS files (it shouldn’t be used too much); here’s how to include them on your page:
<?php include_file('desktop', 'weather', 'css', 'weather'); ?>
The first argument specifies the folder where it is located (note that this is the parent folder of the CSS folder), the second is the name of your CSS file, the third tells Jeedom that it is a CSS file, and the last specifies which plugin it is in.
The modal folder lets you store your PHP files used to display modals. Here’s how to call them from your main page (this code goes in a JavaScript file):
Here’s what you can see:
$('#md_modal').dialog({title: "{{Classe du périphérique}}"}).load('index.php?v=d&plugin=zwave&modal=show.class&id=' + $('.eqLogicAttr[data-l1key=id]').value()).dialog('open')
The first line lets you add a title to your modal
The second line loads your modal and the display. The syntax is quite simple: plugin, your plugin’s ID, modal, the name of your modal without the “php,” and then the parameters you want to pass to it
This isn’t a feature, but in the latest versions of Jeedom, it provides developers with a full JavaScript API (which eliminates the need to write AJAX calls all over the place). I’ll try to write an article explaining the various features, but you can already find the code here.
That covers the details of the desktop folder. I realize it’s not the most comprehensive (I’ll try to expand it based on the various requests I receive), but I hope it will help you get started on creating plugins for Jeedom.
$('body').delegate('.helpSelectCron','click',function() {
var el = $(this).closest('.schedule').find('.scenarioAttr[data-l1key=schedule]')
jeedom.getCronSelectModal({},function (result) {
el.value(result.value)
})
})
When you click the assistant button, the input field where you can type appears, and then the assistant is launched. Once configuration is complete in the assistant, the result is retrieved and written to the previously selected input field
By far the most important folder in your plugin, it can contain up to 4 subfolders.
Note: Throughout this section, your plugin’s ID will be referred to as: plugin_id
Contains the associated PHP files. I’ve gotten into the habit of including, for example, an include file if—of course—you have multiple class files or third-party files to include.
This folder can contain two subfolders, “dashboard” and “mobile.” Jeedom automatically scans this folder for widgets, so if you’re using specific widgets, this is where you should place their HTML files.
This is where your translation should be located as a JSON file (it’s best to check out the plugin, for example, Z-Wave to view the file format)
This folder is for all your AJAX files. Here is a sample AJAX file:
<?php
/* This file is part of Jeedom.
*
* Jeedom is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Jeedom is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Jeedom. If not, see <http://www.gnu.org/licenses/>.
*/
try {
require_once dirname(__FILE__) . '/../../../../core/php/core.inc.php';
include_file('core', 'authentification', 'php');
if (!isConnect('admin')) {
throw new Exception(__('401 - Accès non autorisé', __FILE__));
}
if (init('action') == 'votre action') {
ajax::success($result);
}
throw new Exception(__('Aucune methode correspondante à : ', __FILE__) . init('action'));
/* * *********Catch exeption*************** */
} catch (Exception $e) {
ajax::error(displayExeption($e), $e->getCode());
}
?>
This is a very important file—it’s the core of your plugin. This is where the two required classes for your plugin go:
plugin\_idplugin\_idCmdThe first one should inherit from the eqLogic class, and the second from cmd. Here is a template:
<?php
/* This file is part of Jeedom.
*
* Jeedom is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Jeedom is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Jeedom. If not, see <http://www.gnu.org/licenses/>.
*/
/* * ***************************Includes********************************* */
require_once dirname(__FILE__) . '/../../../../core/php/core.inc.php';
class plugin_id extends eqLogic {
/* * *************************Attributs****************************** */
/* * ***********************Methode static*************************** */
/* * *********************Methode d'instance************************* */
/* * **********************Getteur Setteur*************************** */
}
class plugin_idCmd extends cmd {
/* * *************************Attributs****************************** */
/* * ***********************Methode static*************************** */
/* * *********************Methode d'instance************************* */
/* * **********************Getteur Setteur*************************** */
}
?>
For information on defining Jeedom classes, please refer to this website
The only required method is the instance method on the cmd_execute class. Here is an example using the S.A.R.A.H plugin:
public function execute($_options = array()) {
if (!isset($_options['title']) && !isset($_options['message'])) {
throw new Exception(__("Le titre ou le message ne peuvent être tous les deux vide", __FILE__));
}
$eqLogic = $this->getEqLogic();
$message = '';
if (isset($_options['title'])) {
$message = $_options['title'] . '. ';
}
$message .= $_options['message'];
$http = new com_http($eqLogic->getConfiguration('addrSrvTts') . '/?tts=' . urlencode($message));
return $http->exec();
}
Here’s a fairly simple but comprehensive example. The principle is as follows: if the command is an action or a piece of information (but not just an event, and its cache has expired), then Jeedom calls this method.
In our example here, this is a command to make S.A.R.A.H speak, where the plugin retrieves the parameters from $_options (note that this is an array and its attributes change depending on the command’s type: “color” for a “color” type, “slider” for a “slider” type, “title” and “message” for a “message” type, and empty for an “other” type).
That covers the required part; here are some additional features you can use (with examples):
This function can be used in the command system or in the device, depending on your needs. Here is an example for the device:
public function toHtml($_version = 'dashboard') {
$replace = $this->preToHtml($_version);
if (!is_array($replace)) {
return $replace;
}
$version = jeedom::versionAlias($_version);
$replace['#forecast#'] = '';
if ($version != 'mobile' || $this->getConfiguration('fullMobileDisplay', 0) == 1) {
$forcast_template = getTemplate('core', $version, 'forecast', 'weather');
for ($i = 0; $i < 5; $i++) {
$replaceDay = array();
$replaceDay['#day#'] = date_fr(date('l', strtotime('+' . $i . ' days')));
if ($i == 0) {
$temperature_min = $this->getCmd(null, 'temperature_min');
} else {
$temperature_min = $this->getCmd(null, 'temperature_' . $i . '_min');
}
$replaceDay['#low_temperature#'] = is_object($temperature_min) ? $temperature_min->execCmd() : '';
if ($i == 0) {
$temperature_max = $this->getCmd(null, 'temperature_max');
} else {
$temperature_max = $this->getCmd(null, 'temperature_' . $i . '_max');
}
$replaceDay['#hight_temperature#'] = is_object($temperature_max) ? $temperature_max->execCmd() : '';
$replaceDay['#tempid#'] = is_object($temperature_max) ? $temperature_max->getId() : '';
if ($i == 0) {
$condition = $this->getCmd(null, 'condition');
} else {
$condition = $this->getCmd(null, 'condition_' . $i);
}
$replaceDay['#icone#'] = is_object($condition) ? self::getIconFromCondition($condition->execCmd()) : '';
$replaceDay['#conditionid#'] = is_object($condition) ? $condition->getId() : '';
$replace['#forecast#'] .= template_replace($replaceDay, $forcast_template);
}
}
$temperature = $this->getCmd(null, 'temperature');
$replace['#temperature#'] = is_object($temperature) ? $temperature->execCmd() : '';
$replace['#tempid#'] = is_object($temperature) ? $temperature->getId() : '';
$humidity = $this->getCmd(null, 'humidity');
$replace['#humidity#'] = is_object($humidity) ? $humidity->execCmd() : '';
$pressure = $this->getCmd(null, 'pressure');
$replace['#pressure#'] = is_object($pressure) ? $pressure->execCmd() : '';
$replace['#pressureid#'] = is_object($pressure) ? $pressure->getId() : '';
$wind_speed = $this->getCmd(null, 'wind_speed');
$replace['#windspeed#'] = is_object($wind_speed) ? $wind_speed->execCmd() : '';
$replace['#windid#'] = is_object($wind_speed) ? $wind_speed->getId() : '';
$sunrise = $this->getCmd(null, 'sunrise');
$replace['#sunrise#'] = is_object($sunrise) ? $sunrise->execCmd() : '';
$replace['#sunid#'] = is_object($sunrise) ? $sunrise->getId() : '';
if (strlen($replace['#sunrise#']) == 3) {
$replace['#sunrise#'] = substr($replace['#sunrise#'], 0, 1) . ':' . substr($replace['#sunrise#'], 1, 2);
} else if (strlen($replace['#sunrise#']) == 4) {
$replace['#sunrise#'] = substr($replace['#sunrise#'], 0, 2) . ':' . substr($replace['#sunrise#'], 2, 2);
}
$sunset = $this->getCmd(null, 'sunset');
$replace['#sunset#'] = is_object($sunset) ? $sunset->execCmd() : '';
if (strlen($replace['#sunset#']) == 3) {
$replace['#sunset#'] = substr($replace['#sunset#'], 0, 1) . ':' . substr($replace['#sunset#'], 1, 2);
} else if (strlen($replace['#sunset#']) == 4) {
$replace['#sunset#'] = substr($replace['#sunset#'], 0, 2) . ':' . substr($replace['#sunset#'], 2, 2);
}
$wind_direction = $this->getCmd(null, 'wind_direction');
$replace['#wind_direction#'] = is_object($wind_direction) ? $wind_direction->execCmd() : 0;
$refresh = $this->getCmd(null, 'refresh');
$replace['#refresh_id#'] = is_object($refresh) ? $refresh->getId() : '';
$condition = $this->getCmd(null, 'condition_now');
$sunset_time = is_object($sunset) ? $sunset->execCmd() : null;
$sunrise_time = is_object($sunrise) ? $sunrise->execCmd() : null;
if (is_object($condition)) {
$replace['#icone#'] = self::getIconFromCondition($condition->execCmd(), $sunrise_time, $sunset_time);
$replace['#condition#'] = $condition->execCmd();
$replace['#conditionid#'] = $condition->getId();
$replace['#collectDate#'] = $condition->getCollectDate();
} else {
$replace['#icone#'] = '';
$replace['#condition#'] = '';
$replace['#collectDate#'] = '';
}
if ($this->getConfiguration('modeImage', 0) == 1) {
$replace['#visibilityIcon#'] = "none";
$replace['#visibilityImage#'] = "block";
} else {
$replace['#visibilityIcon#'] = "block";
$replace['#visibilityImage#'] = "none";
}
$html = template_replace($replace, getTemplate('core', $version, 'current', 'weather'));
cache::set('widgetHtml' . $_version . $this->getId(), $html, 0);
return $html;
}
There are several interesting things here:
To convert the requested version to a dashboard or mobile view (e.g., “mview” becomes “mobile”), which allows you to add the names of objects to the views, for example.
$_version = jeedom::versionAlias($_version);
Retrieving a command template; in this case, the command template: plugins/weather/core/template/$_version/forecast.html (where $$_version$ is either “mobile” or “dashboard”)
$forcast_template = getTemplate('core', $_version, 'forecast', 'weather');
Here, replace the tags previously filled in the $replace section of the HTML to contain the values
$html_forecast .= template_replace($replace, $forcast_template);
This retrieves the command with the logical_id: temperature_min
$this->getCmd(null, 'temperature_min');
This allows you to set the value in the tag only if the command was successfully received
$replace['#temperature#'] = is_object($temperature) ? $temperature->execCmd() : '';
Important note: This retrieves the customizations made by the user on the General → Display page and reapplies them to the template
$parameters = $this->getDisplay('parameters');
if (is_array($parameters)) {
foreach ($parameters as $key => $value) {
$replace['#' . $key . '#'] = $value;
}
}
Caching the widget: to ensure it loads faster on the next request, note the 0 here, which indicates an infinite cache lifetime; otherwise, the duration is in seconds (we’ll see in the next section how the weather plugin updates its widget).
cache::set('weatherWidget' . $_version . $this->getId(), $html, 0);
Finally, sending the HTML to Jeedom:
return $html;
You also need to tell Jeedom what customization options your widget supports. It’s a bit complex (though not too much), but it’s generally flexible and easy to set up.
It works the same way on your device or command; it’s a static attribute of the $_widgetPossibility class that must be a multidimensional array, but this is where things get complicated if one dimension of the array is set to true or false. In that case, it assumes that all possible children have that value (I’ll give an example).
First, here are the scenarios where you’ll need to use this: if your class that inherits from eqLogic or cmd has a toHtml function; otherwise, there’s no point in reading further.
When creating or deleting your objects (devices, commands, or others) in Jeedom, Jeedom may call several methods before or after the action:
preInsert ⇒ Method called before your object is createdpostInsert ⇒ Method called after your object is createdpreUpdate ⇒ Method called before your object is updatedpostUpdate ⇒ Method called after your object is updatedpreSave ⇒ Method called before your object is saved (i.e., when it is created or updated)postSave ⇒ Method called after your object is savedpreRemove ⇒ Method called before your object is deletedpostRemove ⇒ Method called after your object is deletedFor example, using the weather plugin again, here’s how to create commands or update them after saving (this is a simplified example):
public function postUpdate() {
$weatherCmd = $this->getCmd(null, 'temperature');
if (!is_object($weatherCmd)) {
$weatherCmd = new weatherCmd();
}
$weatherCmd->setName(__('Température', __FILE__));
$weatherCmd->setLogicalId('temperature');
$weatherCmd->setEqLogic_id($this->getId());
$weatherCmd->setConfiguration('day', '-1');
$weatherCmd->setConfiguration('data', 'temp');
$weatherCmd->setUnite('°C');
$weatherCmd->setType('info');
$weatherCmd->setSubType('numeric');
$weatherCmd->save();
$cron = cron::byClassAndFunction('weather', 'updateWeatherData', array('weather_id' => intval($this->getId())));
if (!is_object($cron)) {
$cron = new cron();
$cron->setClass('weather');
$cron->setFunction('updateWeatherData');
$cron->setOption(array('weather_id' => intval($this->getId())));
}
$cron->setSchedule($this->getConfiguration('refreshCron', '*/30 * * * *'));
$cron->save();
}
The beginning is fairly standard—creating a command—but the end is more interesting, as it involves setting up a cron job that will call the method weather::updateWeatherData by passing the ID of the device to be updated every 30 minutes by default.
Here is the updateWeatherData method (also simplified):
public static function updateWeatherData($_options) {
$weather = weather::byId($_options['weather_id']);
if (is_object($weather)) {
foreach ($weather->getCmd('info') as $cmd) {
$weather->checkAndUpdateCmd($cmd,$cmd->execute());
}
}
}
Here we can see that when the call is made, the relevant device is retrieved, and then commands are executed to retrieve the values and update them if necessary.
A very important part:
$weather->checkAndUpdateCmd($cmd,$cmd->execute());
At the time of the function checkAndUpdateCmd (which notifies Jeedom of a new value update, triggering all necessary actions: updating the dashboard, checking scenarios…),
For the command class, here’s a quick tip if you’re using the basic JS template. When sending device commands, Jeedom compares the commands and removes any that are in the base definition but not in the new device definition. Here’s how to avoid that:
public function dontRemoveCmd() {
return true;
}
Finally, here are a few tips and tricks:
$eqLogic->batteryStatus(56);
formatValue($_value) which, depending on the type, can reformat it (particularly for binary values)addHistoryValue to force the command to be logged (note that your command must be loggable)