Domotique

Récupération JSON pour WT32

Pour permettre à un ESP32 (comme le WT32) de récupérer le JSON des thermostats via l'URL spécifiée et de l'enregistrer dans le SPIFFS pour alimenter la structure, voici comment vous pourriez structurer votre code côté microcontrôleur.

Étapes générales :

  1. Effectuer une requête HTTP GET à l'URL API Jeedom.
  2. Télécharger et stocker la réponse JSON dans le SPIFFS.
  3. Lire le fichier JSON depuis le SPIFFS et l'analyser pour remplir une structure en mémoire.

Voici un exemple de code pour le WT32/ESP32 en utilisant l'Arduino framework :

Code ESP32 côté Arduino

#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <SPIFFS.h>

// Configuration WiFi
const char* ssid = "Votre_SSID";
const char* password = "Votre_MotDePasse";

// URL de l'API Jeedom
const char* api_url = "http://xxxxxxxxxxxxxxxxxxxx/core/api/jeeApi.php?apikey=xxxxxxxxxxxxxxxxxxxxxxxxxxxi&type=cmd&id=44474";

// Fichier JSON dans le SPIFFS
const char* spiffs_file = "/thermostats.json";

// Structure du thermostat
struct Mode {
    String name;
    int cmd;
};

struct Thermostat {
    int id;
    String name;
    int order_max;
    int order_min;
    Mode modes[10];  // Adapter selon le nombre maximum de modes
    int modeCmdId;
    int statutCmdId;
    int tempCmdId;
    int getConsigneCmdId;
    int putConsigneCmdId;
};

void setup() {
    Serial.begin(115200);

    // Initialiser le SPIFFS
    if (!SPIFFS.begin(true)) {
        Serial.println("Erreur SPIFFS !");
        return;
    }

    // Connexion WiFi
    WiFi.begin(ssid, password);
    Serial.println("Connexion WiFi...");
    while (WiFi.status() != WL_CONNECTED) {
        delay(1000);
        Serial.print(".");
    }
    Serial.println("\nConnecté au WiFi");

    // Récupérer le JSON
    if (downloadThermostatJson()) {
        Serial.println("JSON récupéré et stocké !");
        parseThermostatJson(); // Lire et analyser le JSON
    } else {
        Serial.println("Erreur lors de la récupération du JSON.");
    }
}

void loop() {
    // Votre logique principale
}

// Télécharger le JSON depuis l'API Jeedom et le stocker dans le SPIFFS
bool downloadThermostatJson() {
    if (WiFi.status() == WL_CONNECTED) {
        HTTPClient http;
        http.begin(api_url);
        int httpCode = http.GET();

        if (httpCode == 200) { // Succès
            String payload = http.getString();

            // Écrire dans le SPIFFS
            File file = SPIFFS.open(spiffs_file, FILE_WRITE);
            if (!file) {
                Serial.println("Erreur d'ouverture du fichier pour écrire.");
                return false;
            }
            file.print(payload);
            file.close();
            return true;
        } else {
            Serial.printf("Erreur HTTP: %d\n", httpCode);
        }
        http.end();
    }
    return false;
}

// Lire le fichier JSON depuis le SPIFFS et l'analyser
void parseThermostatJson() {
    File file = SPIFFS.open(spiffs_file, FILE_READ);
    if (!file) {
        Serial.println("Erreur d'ouverture du fichier JSON.");
        return;
    }

    // Charger le JSON dans un objet ArduinoJson
    StaticJsonDocument<4096> doc; // Adapter la taille au besoin
    DeserializationError error = deserializeJson(doc, file);
    file.close();

    if (error) {
        Serial.printf("Erreur d'analyse JSON: %s\n", error.c_str());
        return;
    }

    // Exemple : parcourir et afficher les thermostats
    JsonArray thermostats = doc.as<JsonArray>();
    for (JsonObject thermostat : thermostats) {
        Serial.printf("Thermostat ID: %d, Name: %s\n", thermostat["id"].as<int>(), thermostat["name"].as<const char*>());
        JsonArray modes = thermostat["modes"];
        for (JsonObject mode : modes) {
            Serial.printf("  Mode: %s, Cmd: %d\n", mode["name"].as<const char*>(), mode["cmd"].as<int>());
        }
    }
}

Points Clés :

  1. SPIFFS : Utilisé pour stocker le JSON localement afin de le réutiliser sans refaire une requête.
  2. ArduinoJson : Permet de traiter et d'analyser les données JSON facilement.
  3. Gestion des erreurs : Vérifiez toujours les retours des fonctions SPIFFS, HTTP et JSON.

Étapes Suivantes :

  1. Adapter les tailles et structures pour s'assurer que tout le contenu est pris en charge.
  2. Optimiser la mémoire si le JSON est très volumineux, en utilisant un parsing par streaming.
  3. Ajouter une logique d'actualisation pour mettre à jour le JSON périodiquement ou sur demande.


Autre variante.

Parsing du JSON dans le code WT32-SC01

Pour alimenter la structure Thermostat, voici un exemple de code en C++ (Arduino) qui parse le JSON et charge les données dans une liste.

Exemple d'utilisation avec ArduinoJson

#include <ArduinoJson.h>
#include <SPIFFS.h>

#define JSON_SIZE 4096 // Ajustez selon la taille maximale de votre JSON

// Définition de la structure Thermostat
typedef struct {
    const char* label;
    int confortCmdId;
    int confort1CmdId;
    int confort2CmdId;
    int ecoCmdId;
    int horsGelCmdId;
    int offCmdId;
    int modeCmdId;
    int statutCmdId;
    int tempCmdId;
    int getConsigneCmdId;
    int putConsigneCmdId;
    int order_max;
    int order_min;
} Thermostat;

// Table de thermostats
Thermostat thermostats[10]; // Ajustez la taille selon vos besoins
int thermostatCount = 0;

void setup() {
    Serial.begin(115200);

    // Initialiser le SPIFFS
    if (!SPIFFS.begin(true)) {
        Serial.println("Erreur SPIFFS");
        return;
    }

    // Charger le JSON
    File file = SPIFFS.open("/thermostats.json", "r");
    if (!file) {
        Serial.println("Erreur lors de l'ouverture du fichier JSON");
        return;
    }

    // Lire le JSON
    StaticJsonDocument<JSON_SIZE> doc;
    DeserializationError error = deserializeJson(doc, file);
    if (error) {
        Serial.print("Erreur de parsing JSON: ");
        Serial.println(error.c_str());
        return;
    }

    // Parcourir les thermostats
    JsonArray array = doc.as<JsonArray>();
    thermostatCount = 0;
    for (JsonObject obj : array) {
        Thermostat& t = thermostats[thermostatCount++];
        t.label = obj["name"];
        t.confortCmdId = obj["modes"][0]["cmd"];
        t.confort1CmdId = obj["modes"][1]["cmd"];
        t.confort2CmdId = obj["modes"][2]["cmd"];
        t.horsGelCmdId = obj["modes"][3]["cmd"];
        t.ecoCmdId = obj["modes"][4]["cmd"];
        t.offCmdId = obj["modes"][5]["cmd"];
        t.modeCmdId = obj["modeCmdId"];
        t.statutCmdId = obj["statutCmdId"];
        t.tempCmdId = obj["tempCmdId"];
        t.getConsigneCmdId = obj["getConsigneCmdId"];
        t.putConsigneCmdId = obj["putConsigneCmdId"];
        t.order_max = obj["order_max"];
        t.order_min = obj["order_min"];
    }

    // Afficher les thermostats chargés
    for (int i = 0; i < thermostatCount; i++) {
        Serial.print("Thermostat ");
        Serial.println(thermostats[i].label);
    }
}

void loop() {
    // Boucle principale
}

Sauvegarde et Lecture dans SPIFFS

Enregistrez le JSON dans SPIFFS à l'aide de la commande suivante pour le tester :

Écriture initiale du JSON

void writeExampleJson() {
    const char* json = R"([
        {
            "id": 113,
            "name": "Salon",
            "order_max": 28,
            "order_min": 9,
            "modes": [
                { "name": "Confort", "cmd": 755 },
                { "name": "Confort -1", "cmd": 8404 },
                { "name": "Confort -2", "cmd": 8405 },
                { "name": "Hors Gel", "cmd": 44440 },
                { "name": "Eco", "cmd": 756 },
                { "name": "Off", "cmd": 752 }
            ],
            "modeCmdId": 10001,
            "statutCmdId": 10002,
            "tempCmdId": 10003,
            "getConsigneCmdId": 10004,
            "putConsigneCmdId": 10005
        }
    ])";

    File file = SPIFFS.open("/thermostats.json", "w");
    if (!file) {
        Serial.println("Erreur lors de la création du fichier JSON");
        return;
    }

    file.print(json);
    file.close();
}

Appelez cette fonction une fois dans le setup() pour initialiser le fichier JSON dans SPIFFS.

 

 

 

Description du plugin Script de Jeedom

Plugin permettant d’exécuter des scripts (shell, php, ruby…), des requêtes http, de récupérer des informations dans du XML ou JSON.

Configuration du plugin

La configuration est très simple, après téléchargement du plugin, il vous suffit de l’activer et c’est tout.

configuration

La seule option est l’endroit où Jeedom met les scripts par défaut, il est conseillé de ne pas y toucher.

Configuration des équipements

La configuration des équipements Script est accessible à partir du menu plugin/Programmation

Voilà à quoi ressemble la page du plugin Script (ici avec déjà un équipement) :

liste des équipements

Vous retrouvez ici la liste de vos Scripts. Une fois que vous cliquez sur un équipement vous obtenez :

équipement

Vous retrouvez ici toute la configuration de votre équipement :

  • Nom de l’équipement script : nom de votre équipement script
  • Catégorie : les catégories de l’équipement (il peut appartenir à plusieurs catégories)
  • Activer : permet de rendre votre équipement actif
  • Visible : le rend visible sur le Dashboard
  • Objet parent : indique l’objet parent auquel appartient l’équipement
  • Auto-actualisation : permet de spécifier un Cron d’actualisation automatique pour toutes les commandes de type info.
  • Délai avant d’actualiser les infos suite à une action : Saisir un nombre de seconde

commandes Vous retrouvez ici la liste des commandes :

  • Nom : Ce champ contient le nom que vous souhaitez donner à votre commande/information.
  • Icône : Ce champ permet d’associer une icône à votre nom (dans ce cas Jeedom remplace le nom par l’icône dans le Dashboard).
  • Type de script :
    • Le type http : permet d’envoyer une requête vers un équipement externe sans forcément attendre un retour de cette commande. L’exemple qui servira de support au type http sera la configuration d’une requête vers une Vera pour allumer une lumière.
    • Le type script : sert principalement à lancer des scripts internes à Jeedom. L’exemple qui servira de support au type script sera la configuration du script de monitoring température du raspberry.
    • Le type XML : permet de rapatrier des informations encodées en XML depuis un équipement distant. L’exemple qui servira de support au type XML sera la configuration du script pour interroger un Eco-Device.
    • Le type JSON : permet de rapatrier des informations encodées en JSON depuis un équipement distant. L’exemple qui servira de support au type JSON sera la configuration du script pour interroger Sickbeard (ou XBMC).
  • le type et le sous-type
  • Le champ requête
    • Ce champ doit contenir la requête en elle-même, ou le chemin du script si le champ “type de script” est script. Le bouton “parcourir” : permet de sélectionner le fichier contenu dans le dossier interne à Jeedom.

      > Ce dossier est accessible en SSH dans ``/var/www/html/plugins/script/data/``. Pour info, la commande SSH pour attribuer les droits ``www-data`` à un fichier est : ``sudo chown www-data:www-data NOMDUSCRIPT.EXTENSION``. A savoir que pour exécuter un script, celui-ci doit avoir les droits www-data.
      
    • Le bouton Editer : permet d’éditer à l’aide d’un éditeur de code interne un des fichiers contenus dans le répertoire permettant l’accès au code du fichier.
    • Le bouton Nouveau : permet de créer un fichier de commande.

      > Ne pas oublier de saisir le nom du fichier ainsi que son extension complète sous peine de voir votre superbe script ne pas fonctionner. Sans extension Jeedom ne saura pas reconnaître le langage associé à votre fichier. CF : Généralité
      
    • Le bouton Supprimer : permet de supprimer un fichier de commande.
  • Le champ Options : Champ avec des options variables suivant le choix du type de script.
  • unité : unité de la donnée (peut être vide).
  • min/max : bornes de la donnée (peuvent être vides).
  • historiser : permet d’historiser la donnée.
  • afficher : permet d’afficher la donnée sur le Dashboard.

Important

Il faut éviter, autant que possible, dans le chemin du script ou dans les paramètres de celui-ci les caractères spéciaux. Les caractères autorisés étant : les chiffres, les lettres (majuscule ou minuscule)

Important

Vous pouvez dans le champs requete (pour http, json, xml) mettre du json, il faut juste le faire preceder de json::, exemple json::{"clef":"valeur"}

exemple

Permet d’appeler une url ou de récupérer le retour d’une URL.

  • une case à cocher “Ne pas vérifier SSL” : si cochée, permet à Jeedom de ne pas envoyer les champs “Utilisateur” et “Mot de passe” à la requête. Jeedom ne cherchera pas à s’identifier au site/machine distant.
  • une case à cocher “Autoriser réponse vide” : si cochée, permet à Jeedom de ne pas attendre de réponse, ou d’ignorer toute réponse à la trame émise. En général, on coche si Jeedom nous renvoi un “Curl error : Empty reply from server”.
  • une case à cocher “Ne jamais remonter les erreurs” : permet de ne pas remonter d’alerte en cas d’erreur.
  • un champ “timeout” : sans être renseigné, le timeout de la requête vaut par défaut 2 secondes, sinon il vaut la valeur renseignée.
  • un champ “Essais au maximum” : 4 essais au maximum par défaut.
  • un champ “Utilisateur” : pour renseigner un nom d’utilisateur.
  • un champ “Mot de passe” : pour renseigner un mot de passe.

Le choix HTML

script HTML

Permet de passer une page Web (fichier HTML) pour récupérer une valeur dessus. La syntaxe est la même que pour jQuery.

Le champ option possède un champ “URL du fichier HTML” : ce champ contient donc le lien vers la machine hébergeant le fichier HTML en question.

Le choix XML

script XML

Permet de récupérer du XML et d’aller chercher spécifiquement une valeur dedans.

Le champ option possède un champ “URL du fichier XML” : ce champ contient donc le lien vers la machine hébergeant le fichier XML en question.

Important

Il n’est possible de récupérer que des valeurs, les attributs ne peuvent être récupérés.

Le choix JSON

script JSON

Permet de récupérer du JSON et d’aller chercher spécifiquement une valeur dedans.

Le champ option possède un champ “URL du fichier JSON” : ce champ contient donc le lien vers la machine hébergeant le fichier JSON en question.

Exemples

HTTP : Pilotage d’une Vera

L’exemple est basé sur une Vera et consiste à piloter une ampoule dimmable. Je ne vais pas m’étendre sur la manière de piloter une Vera par requête http, le forum TLD est rempli de réponses. De plus, l’exemple correspond à mon type de matériel et devra être adapté au vôtre.

Tip

Une méthode pour ceux qui tâtonnent pour l’écriture de requêtes http, valider d’abord la syntaxe dans votre navigateur et seulement ensuite passer à la configuration sous Jeedom. Quand un script Action ne fonctionne pas, passer en script Info/Autre permet de voir l’erreur retournée.

Allons-y :

  • On crée un équipement : par exemple LUM CUISINE (je pense qu’on a tous une cuisine sous la main)
  • On l’associe à un objet parent : par exemple VERA, moi ça me permet de centraliser toutes les commandes liées à la VERA sur un unique parent.
  • Choisissez votre catégorie.
  • Activez votre équipement, ne cochez pas visible, on verra un peu plus tard comment l’associer à un virtuel (plus sexy, plus WAF)
  • Pour l’auto-actualisation, ne rien mettre, il s’agit d’une commande impulsionnelle liée à un appui sur un bouton ou un scénario !
  • Ajoutez une commande script
  • Pensez à sauvegarder

Explications :

  • Nom : 100 % car on va allumer une lumière à pleine puissance
  • Type de script : http
  • Type : Action (c’est une commande)
  • Sous type : défaut
  • Requête :
http://<IP_VERA>:3480/data_request?id=lu_action&output_format=json&DeviceNum=12&serviceId=urn:upnp-org:serviceId:Dimming1&action=SetLoadLevelTarget&newLoadlevelTarget=100

Tip

le “100” à la fin de la requête correspond au pourcentage de puissance à affecter donc mettre “0” à la fin de la requête correspond à éteindre l’ampoule.

Le bouton “test” vous permet de tester votre commande !

Vous pouvez donc multiplier les commandes dans le même équipement en mettant par exemple une commande à 60 % pour une lumière tamisée, créer une troisième à 30 % pour les déplacements nocturnes à associer dans un scénario, …

Il est aussi possible de créer une commande de type slider en mettant le tag #slider# dans la requête :

http://<IP_VERA>:3480/data_request?id=lu_action&output_format=json&DeviceNum=12&serviceId=urn:upnp-org:serviceId:Dimming1&action=SetLoadLevelTarget&newLoadlevelTarget=#slider#

Tip

Si votre commande est de type message vous pouvez utiliser les tags #message# et #title#, idem pour une commande de type couleur avec le tag #color#, ou de type slider avec #slider# ou liste avec #select#

HTTP : Envoyer une notification à XBMC

But : Envoyer une notification vers XBMC lors de l’ouverture d’une porte d’entrée.

  • Nom : PUSH XBMC
  • Type de script : http
  • Type : Action (c’est une commande)
  • Sous-type : défaut
  • Requête :
http://IP_DE_XBMC:8080/jsonrpc?request={ %22jsonrpc%22:%222.0%22,%22method%22:%22GUI.ShowNotification%22,%22params%22:{ %22title%22:%22Mouvement%20Détecté%22,%22message%22:%22Porte%20Entrée%22},%22id%22:1}

A vous de tester ça dans un scénario par exemple !

API XBMC ici (seuls les champs notés “required” sont obligatoires)

But : Envoyer une notification vers XBMC lorsque la température tombe sous un certain seuil

Prenez l’exemple ci-dessus :

  • remplacez Mouvement%20Détecté par Risque%20de%20gel
  • remplacez Porte%20Entrée par Température%20extérieur%20:%20#[EXTERIEUR][EXTERIEUR][TEMPERATURE]#

Testez sur un scénario #[EXTERIEUR][EXTERIEUR][TEMPERATURE]# < 5 par exemple

Action : Lancez le script, via un équipement virtuel, lié à votre script !

SCRIPT

Le plus sympa mais pas le plus simple à expliquer.

Prérequis : savoir développer un script en php, python, perl ou ruby.

IMPORTANT

L’extension de votre script doit absolument correspondre à son type. En effet Jeedom se base sur l’extension du script pour l’exécutable à lancer

Si le nom de votre fichier ne contient pas :

  • .php .py .pl .rb

Le plugin script lancera un shell qui l’exécutera en se basant sur la directive de la 1ère ligne ( shebang ). Exemple :

#!/bin/csh -f
#!/bin/ksh
#!/usr/bin/env python3
#!/usr/bin/env php
#!/usr/bin/env node
etc ...

Le script de monitoring température du Raspberry va servir d’exemple pour l’utilisation du type de script : Script

Après avoir téléchargé le script, le bouton “Parcourir” vous permet de sélectionner le fichier temp_rasp.php.

Par curiosité, vous pouvez aller voir le contenu du fichier en appuyant sur le bouton “Editer”, vous devriez obtenir le code suivant :

Ceci est un script php qui peut tout à fait être réutilisé hors Jeedom !

<?php
$temp = shell_exec("cat /sys/class/thermal/thermal_zone0/temp");
$temp = $temp / 1000;
$temp = round($temp,1);
echo $temp;
?>

<?php
$temp = shell_exec("cat /sys/class/thermal/thermal_zone0/temp");
$temp = $temp / 1000;
$temp = round($temp,1);
echo $temp;
?>

Note : concrètement, c’est la fonction php “echo” qui va donner la valeur à Jeedom

Les paramètres

Récupérer les infos de Jeedom pour les exploiter dans un script. La récupération dépend du type de script utilisé :

  • Dans la ligne : /var/www/html/plugins/script/data/my_script.php my_value, l’argument my_value est une chaîne de caractères (fixe) récupérée dans le script php dans le tableau d’arguments $argv voir https://www.php.net/manual/fr/reserved.variables.argv.php pour plus de détails.
  • Nous avons vu précédemment qu’il était possible de récupérer des valeurs dynamiques à partir de Jeedom.
  • Dans la ligne : /var/www/html/plugins/script/data/radio.py VOL #slider#, l’argument #slider# est récupéré de cette façon $argv[2]. Au moment de l’exécution du script par Jeedom, il remplacera automatiquement #slider# par la valeur (numérique) du slider. cf Google pour plus de détails sur la récupération de paramètres en Python.

  • Plus fort : Potentiellement, toutes les variables accessibles par Jeedom sont exploitables par le plugin script :
    • Vous voulez récupérer la valeur de la température de la cuisine pour l’historiser en dehors de Jeedom ?
    • Passer #[MAISON][CUISINE][Température]# comme paramètre au script et Jeedom le remplacera par la valeur lue lors de l’envoi.

Préconisation pour tester les paramètres dans le script php :

if (isset($argv)) {
    foreach ($argv as $arg) {
        $argList = explode('=', $arg);
        if (isset($argList[0]) && isset($argList[1])) {
            $_GET[$argList[0]] = $argList[1];
        }
    }
}

XML simple

Voici le format du XML type :

<root>
    <led0>1</led0>
    <leds>
        <led1>toto</led1>
    </leds>
</root>
Si vous voulez la valeur de la led0 dans requête vous mettez led0. Si vous voulez la valeur de la led1 qui est le fils de leds vous mettez leds > led1.

Notez que l’élément racine <root> n’est pas à préciser dans le champ requête.

XML complexe

<root>
    <led0>1</led0>
    <leds>
        <led1>toto</led1>
    </leds>
    <leds>
        <led1>tata</led1>
    </leds>
</root>
la syntaxe est :

leds > 1 > led1 qui donne en réponse tata, 1 étant le numéro de rang du tableau !

XML plus complexe

AKT_Data ID="SMS-Liste" ZeitSt="01.05.2017 18:55">
    <MesPar DH="HBCHa" StrNr="2167" Typ="02" Var="02">
        <Name>Tresa - Ponte Tresa, Rocchetta</Name>
        <Datum>01.05.2017</Datum>
        <Zeit>18:50</Zeit>
        <Wert>268.56</Wert>
        <Wert dt="-24h">268.51</Wert>
        <Wert Typ="delta24">0.051</Wert>
        <Wert Typ="m24">268.52</Wert>
        <Wert Typ="max24">268.56</Wert>
        <Wert Typ="min24">268.50</Wert>
    </MesPar>
    <MesPar DH="HBCHa" StrNr="2265" Typ="03" Var="02">
        <Name>Inn - Tarasp</Name>
        <Datum>01.05.2017</Datum>
        <Zeit>18:50</Zeit>
        <Wert>4.85</Wert>
        <Wert dt="-24h">7.98</Wert>
        <Wert Typ="delta24">-3.130</Wert>
        <Wert Typ="m24">6.15</Wert>
        <Wert Typ="max24">7.98</Wert>
        <Wert Typ="min24">4.85</Wert>
    </MesPar>
    <MesPar DH="HBCHa" StrNr="2270" Typ="02" Var="32">
        <Name>Doubs - Combe des Sarrasins</Name>
        <Datum>01.05.2017</Datum>
        <Zeit>18:00</Zeit>
        <Wert>500.65</Wert>
        <Wert dt="-24h">500.65</Wert>
        <Wert Typ="delta24">0.000</Wert>
        <Wert Typ="m24">500.65</Wert>
        <Wert Typ="max24">500.65</Wert>
        <Wert Typ="min24">500.64</Wert>
    </MesPar>
</AKT_Data>

Pour récupérer l’information du champ Wert du 1er bloc:

MesPar>0>Wert>0 qui retourne donc “268.56 “

Pour retourner l’élément suivant dans la “structure” Wert, il faut simplement indiquer le numéro d’ordre dans la structure. Ce qui donne pour l’élément <Wert Typ="delta24">0.051</Wert> le code suivant :

MesPar>1>Wert>2

Pour passer au bloc “MesPar” suivant, il faut donc changer l’index en conséquence : le 1 par 2, par exemple.

ATTENTION : Si dans le fichier XML l’ordre change, la requête ne fonctionne plus. Il faudra réadapter la requête en fonction de l’ordre retourné.

JSON

A l’instar du type XML, il est possible de lire des informations issues d’un retour JSON.

Pour expliquer, je vais me baser sur les informations JSON avec l’application Sickbeard (bouh … cpasbien) mais ici seule la technique prime, pas l’outil!

L’accès à ce fichier est possible grâce à l’URL suivante :

http://<IP_DELAMACHINEQUIEBERGESICKBEARD>:8083/api/XXXX/?cmd=history&limit=3

NOTE : XXXX est le numéro de clef api propre à chaque SICKBEARD.

Tout d’abord, avant de se lancer dans la configuration du plugin script JSON, il s’agit d’identifier correctement les infos à récupérer., car ici nous allons intégrer une notion de tableau dans les retours.

Valider l’affichage des informations à partir de votre navigateur (test sous Chrome).

Exemple de retour :

{
    "data": [
        {
            "date": "2014-09-10 01:37",
            "episode": 4,
            "provider": "RNT",
            "quality": "SD TV",
            "resource": "XXX",
            "resource_path": "XXXX",
            "season": 2,
            "show_name": "Totovaalaplage S2E4",
            "status": "Downloaded",
            "tvdbid": XXXXX
        },
        {
            "date": "2014-09-10 01:36",
            "episode": 3,
            "provider": "RNT",
            "quality": "SD TV",
            "resource": "XXXX",
            "resource_path": "XXX",
            "season": 2,
            "show_name": "Totovaalaplage S2E3",
            "status": "Downloaded",
            "tvdbid": XXXXX
        },
        {
            "date": "2014-09-10 01:21",
            "episode": 1,
            "provider": "Cpasbien",
            "quality": "SD TV",
            "resource": "XXXX",
            "resource_path": "XXXX",
            "season": 1,
ICI -->     "show_name": "Totovaplusauski mais Totovaalaplage S1E1",
            "status": "Snatched",
            "tvdbid": XXXX
        }
    ],
    "message": "",
    "result": "success"
}
Dans l’hypothèse où nous voudrions retourner le show_name du 3ème élément en php (repéré ICI) , il faudrait faire : data > 2 > show_name, l’index du tableau de retour commençant à Zéro.

Dans cet exemple, le bouton “Tester” nous retournera “Totovaplusauski mais Totovaalaplage S1E1”.

Précisions :

Notez la syntaxe de la commande Requête, elle est de type élément0 > index du tableau > élément1

Inconvénients :

  • cette méthode ne permet que de récupérer un seul élément à la fois.
  • Si on désire retourner l’ensemble des valeurs de “show_name”, ce n’est malheureusement pas possible, il faudra dupliquer le script autant de fois que nécessaire.

HTML

Ici nous allons essayer de récupérer la dernière VDM.

Tout d’abord il faut configurer l’url :

http://www.viedemerde.fr

Ensuite il faut trouver le “chemin” de la dernière VDM. Pour ce faire, il faut aller sur le site puis faire clic droit sur l’élément voulu puis inspecter l’élément, on obtient :

Exemple HTML 1

Là c’est la partie la plus complexe et qui demande un peu d’analyse. Ici mon texte est dans une balise “a” qui est lui dans un élément de type p qui est une div de class “post article”. Il faut donc que je sélectionne le premier élément div de class “post” et “article” puis le premier élément p et que je récupère tout ce qui est dans les balises “a” qu’il contient. J’ai donc : “div.post.article:first p:first a”.

On obtient donc :

Exemple HTML VDM

Pour une actualisation en temps réel, il est possible de mettre un Cron de mise à jour.

Tip

Lors de la mise en place d’un Cron de mise à jour, Jeedom va automatiquement cocher la case Évènement, c’est tout à fait normal.

Voilà ensuite vous pouvez imaginer un scénario qui vous envoie par SMS la dernière VDM.

Création d'un plugin pour Jeedom

Création plugin partie 1 : l’arborescence

Voici sa structure :
tout d’abord un dossier du nom de votre plugin (son identifiant unique plus exactement) qui doit contenir les sous-dossiers suivants :

  • 3rdparty : Dossier contenant les librairies externes utilisées dans le plugin (exemple pour le plugin SMS une librairie pour la communication série en php).
  • core : Dossier contenant tous les fichiers de fonctionnement interne.
    • class : Dossier contenant la classe du plugin.
    • php : Dossier pouvant contenir des fonctions ne devant pas forcément appartenir à une classe (souvent utilisé pour permettre l’inclusion de multiples classes ou fichiers de configuration en une fois).
    • config : Fichier de configuration du plugin.
    • ajax : Dossier contenant les fichiers cibles d’appels AJAX.
    • i18n : Dossier contenant les fichiers .json de traduction du plugin.
    • template : Dossier contenant les templates html pour des tuiles sépcifiques aux équipements du plugin, dans les sous-dossier dashboard et mobile.
  • desktop : Dossier contenant la vue “bureau” du plugin (en opposition avec la vue “mobile”).
    • js : Dossier contenant tous les fichiers de type javascript pour l’interface du plugin.
    • php : Dossier contenant tous les fichiers de type php pour l’interface du plugin.
    • css : Si besoin, tous les fichiers css du plugin, y compris éventuellement des fonts.
    • modal : Dossier contenant le code des modals du plugin.
    • img : Dossier pour les images (png, jpg etc) nécessaires au plugin.
  • plugin_info : Contient les fichiers permettant à Jeedom de qualifier le plugin, de faire son installation et sa configuration.
    • info.json : Fichier contenant les informations de base du plugin .Il est obligatoire sinon Jeedom ne verra pas le plugin. Il contient entre autre l’identifiant du module, la description, les instructions d’installation…​
    • install.php : Fichier contenant (si besoin) les méthodes d’installation et de désinstallation du plugin.
    • configuration.php : Fichier contenant les paramètres à configurer du plugin indépendants des équipements de celui-ci (exemple pour le module Zwave l’ip du Raspberry Pi ayant la carte Razberry)
  • docs : Doit contenir la doc du plugin au format markdown, la racine et le fichier index.md. Toutes les images sont dans docs/images. La doc elle-même est dans un dossier en fonction de la langue (ex en francais : docs/fr\_FR)
  • ressources : Dossier pour d’éventuels démon et dépendances.
  • data : Dossier utilisé pour des fichiers générés par le plugin propre au Jeedom de l’utilisateur.

Pour ce qui est de la convention de nommage des fichiers voici les impératifs :

  • les fichiers de class php doivent obligatoirement se finir par .class.php
  • si ce n’est pas géré par un fichier d’inclusion, le nom du fichier doit être nom\_class.class.php
  • les fichiers servant uniquement de point d’entrée pour inclure de multiples fichiers doivent se finir par .inc.php
  • les fichiers de configuration doivent se finir par .config.php

Voici les recommandations :

  • les fichiers de type AJAX doivent se finir par .ajax.php
  • le nom de la première page de vue d’un plugin doit être le même que l’ID du plugin
  • le nom du fichier JS (s’il y en a un) de la première page de vue du plugin doit être l’ID du plugin

Création plugin partie 2 : dossier plugin info

info.json

Voir ici

install.php

Fichier donnant les instructions d’installation d’un plugin :

Il est composé de la manière suivante :

La première partie commentée contient la licence (c’est mieux). Celle utilisée ici indique que le fichier appartient à Jeedom et qu’il est open source Ensuite vient l’inclusion du core de Jeedom (cela permet d’accéder aux fonctions internes) Ensuite viennent les 3 fonctions :

  • pluginid_install() : méthode permettant d’installer le plugin. Ici l’installation ajoute une tâche cron à Jeedom
  • pluginid_update() : méthode permettant d’installer le plugin. Utilisé ici pour redémarrer la tache cron
  • pluginid_remove() : méthode permettant de supprimer le plugin. Ici la fonction supprime la tâche cron de Jeedom lors de la désinstallation

Exemple :

<?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.php

Fichier permettant de demander des informations de configuration à l’utilisateur :

Le fichier est constitué de :

  • La licence comme précédemment
  • L’inclusion du core de Jeedom
  • La vérification que l’utilisateur est bien connecté (j’inclue le fichier 404 car ce fichier est un fichier de type vue)

Ensuite vient le paramètre demandé (il peut en avoir plusieurs), c’est une syntaxe standard Bootstrap pour les formulaires, les seules particularités à respecter sont la classe (configKey) à mettre sur l’élément de paramètre ainsi que le “data-l1key” qui indique le nom du paramètre. Pour récupérer la valeur de celui-ci ailleurs dans le plugin il suffit de faire : config::byKey(NOM_PARAMETRE, PLUGIN_ID)

Exemple :

<?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>

Création plugin partie 3 : dossier desktop

PHP

Ce dossier contient la vue à proprement parler. Dedans on retrouve obligatoirement la page de configuration du plugin (celle qui apparaîtra quand l’utilisateur fera plugin ⇒ catégorie ⇒ votre plugin). Il est conseillé de nommer celle-ci avec l’id de votre plugin. Il peut aussi contenir le panel (page que l’utilisateur trouvera dans accueil → nom de votre plugin).

Tous les fichiers dans ce dossier doivent finir par .php et doivent obligatoirement commencer par :

<?php
if (!isConnect('admin')) {
    throw new Exception('401');
 }
 sendVarToJS('eqType', 'mail');
 ?>

Une fois sur cette page vous aurez accès en php à toutes les fonctions du core de jeedom (voir ici ) ainsi qu’à celles de tous les modules installés donc le vôtre aussi.

Toutes ces pages étant des vues elles utilisent principalement la syntaxe HTML. Pour tout ce qui est présentation, Jeedom se base principalement sur bootstrap donc toute la documentation est applicable.

Pour simplifier la création de plugin vous pouvez inclure dans votre page le script javascript de template pour les plugins :

<?php include_file('core', 'plugin.template', 'js'); ?>

A mettre tout en bas de votre page et utile uniquement sur la page de configuration de votre plugin. Ce script permet de réduire le javascript obligatoire à une seule fonction (voir partie sur les fichiers JS).

Dans votre page de configuration une syntaxe HTML a été mise en place pour vous simplifier la vie. Donc pour la plupart des plugins vous n’aurez à faire que du HTML pour stocker vos informations en base de données et donc vous en resservir du coté de votre classe.

La syntaxe est assez simple: votre élément (input, select…​) doit avoir la classe css eqLogicAttr (ou cmdAttr pour les commandes) et un attribut indiquant le nom de la propriété :

<input type="text" class="eqLogicAttr form-control" data-l1key="name" placeholder=""/>

Là, par exemple, lors du chargement des données jeedom mettra la valeur du nom de l’équipement dans l’input et lors de la sauvegarde récupérera celle-ci pour la remettre en base de données. Petite astuce certaines propriétés sont en fait des chaînes JSON en BDD (cela permet d’avoir vraiment pas mal de liberté pour le plugin), dans ce cas il suffit de faire :

<input type="text" class="eqLogicAttr form-control" data-l1key="name" placeholder=""/>

Pour la liste des propriétés des équipements et des commandes c’est ici (pour voir les propriétés qui sont de JSON il suffit de regarder le getter ou le setter, si celui-ci prend 2 paramètres alors c’est du JSON)

Dernier point important sur la page de configuration: celle-ci peut contenir autant d’équipements et de commandes que nécessaire. Cependant il y a quelques règles à respecter :

Tous les éléments ayant la classe eqLogicAttr doivent être dans un élément ayant la classe css eqLogic Idem pour les éléments de classe css cmdAttr qui doivent être dans un élément de classe cmd. Toutes les commandes d’un équipement doivent se trouver dans l’élément ayant la classe eqLogic correspondante

JS

Tous les fichiers JS doivent se trouver dans le dossier JS (facile !!!). Il est conseillé de le nommer du même ID que votre plugin (dans la partie configuration, pour le panel vous faîtes comme vous voulez). Ce fichier JS (celui de la configuration du plugin) doit au minimum contenir une méthode addCmdToTable qui prend en paramètre l’objet commande à ajouter. Voici un exemple simple :

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');
}
 

Vous remarquerez qu’il y a une ligne par commande et que celle-ci a bien la classe css cmd. Vous pouvez aussi voir les éléments qui ont la classe cmdAttr.

Plusieurs points importants :

  • cette fonction peut être appelée avec un objet vide (d’où les 3 premières lignes) lors de l’ajout d’une nouvelle commande
  • la dernière ligne permet d’initialiser tous les champs une fois la ligne insérée

Dernier point: un exemple plus complet avec type et sous-type de commande :

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"></option>';
    selRequestType += '<option value="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> </a> ';
    tr += '<a class="btn btn-default editScriptFile cursor input-sm" style="margin-top : 5px;"><i class="fa fa-edit"></i> </a> ';
    tr += '<a class="btn btn-success newScriptFile cursor input-sm" style="margin-top : 5px;"><i class="fa fa-file-o"></i> </a> ';
    tr += '<a class="btn btn-danger removeScriptFile cursor input-sm" style="margin-top : 5px;"><i class="fa fa-trash-o"></i> </a> ';
    tr += '<a class="btn btn-warning bt_shareOnMarket cursor input-sm" style="margin-top : 5px;"><i class="fa fa-cloud-upload"></i> </a> ';
    tr += '</div>';
    tr += '';     tr += '';
    tr += '<input class="cmdAttr form-control tooltips input-sm" data-l1key="unite"  style="width : 100px;" placeholder="" title="">';
    tr += '<input class="tooltips cmdAttr form-control input-sm" data-l1key="configuration" data-l2key="minValue" placeholder="" title=""> ';
    tr += '<input class="tooltips cmdAttr form-control input-sm" data-l1key="configuration" data-l2key="maxValue" placeholder="" title="">';
    tr += '';     tr += '';
    tr += '<span><input type="checkbox" class="cmdAttr" data-l1key="isHistorized" /> <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> </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();
}

Ici on peut remarquer :

  • jeedom.cmd.availableType() va insérer un select avec la liste des types connus (action et info pour le moment)
  • <span class="subType" subType="' + init(\_cmd.subType) + '"><\span> : l’endroit où le select de sous type doit être posé
  • jeedom.cmd.changeType(\$('\#table\_cmd tbody tr:last'), init(\_cmd.subType)) qui permet d’initialiser le sous type avec la bonne valeur

D’autres fonctions javascript peuvent être utilisées :

  • printEqLogic qui prend en paramètre tout l’objet de l’équipement (utile en cas de traitement de données avant de les restituer). Elle est appelée lors de l’affichage des données de l’équipement
  • saveEqLogic qui prend en paramètre l’objet équipement qui va être sauvegardé en base de données (utile si vous devez faire du traitement avant sauvegarde) Dernière chose, pour les fichiers JS, voici comment les inclure de manière propre sur votre page php :
<?php include_file('desktop', 'weather', 'js', 'weather'); ?>

Le premier argument donne le dossier dans lequel le trouver (attention c’est le dossier père du dossier JS), le deuxième le nom de votre javascript, le troisième indique à Jeedom que c’est un fichier JS et le dernier dans quel plugin il se trouve.

CSS

Ce dossier contient vos fichiers CSS (il ne devrait pas être trop utilisé) , voici comment les inclure sur votre page :

<?php include_file('desktop', 'weather', 'css', 'weather'); ?>

Le premier argument donne le dossier dans lequel le trouver (attention c’est le dossier père du dossier CSS), le deuxième le nom de votre fichier css, le troisième indique à Jeedom que c’est un fichier CSS et le dernier dans quel plugin il se trouve.

Le dossier modal vous permet de stocker vos fichiers php destinés à afficher des modals. Voici comment les appeler à partir de votre page principale (ce code se met dans un fichier javascript) :

On peut voir :

$('#md_modal').dialog({title: ""}).load('index.php?v=d&plugin=zwave&modal=show.class&id=' + $('.eqLogicAttr[data-l1key=id]').value()).dialog('open')

La première ligne permet de mettre un titre à votre modal

La deuxième ligne charge votre modal et l’affichage. La syntaxe est assez simple : plugin, l’id de votre plugin, modal, le nom de votre modal sans le php et ensuite les paramètres que vous voulez lui passer

API JS

Ce n’est pas un dossier mais dans les dernières versions de Jeedom celui-ci offre au développeur toute une api javascript (cela évite d’écrire des appels ajax dans tous les sens). J’essayerai de faire un article pour expliquer les différentes fonctionnalités mais vous pouvez déjà trouver le code ici.

Voilà pour les détails du dossier desktop. Je me doute qu’il n’est pas des plus complets (j’essayerai de le compléter en fonction des différentes demandes reçues) mais j’espère que grâce à lui vous pourrez commencer à faire des plugins pour Jeedom.

Trucs et astuces

Assitant cron

$('body').delegate('.helpSelectCron','click',function() {
  var el = $(this).closest('.schedule').find('.scenarioAttr[data-l1key=schedule]')
  jeedom.getCronSelectModal({},function (result) {
    el.value(result.value)
  })
})

Quand on clique sur le bouton assistant, on récupère l’input dans lequel écrire puis on appelle l’assistant. Une fois la configuration finie dans l’assistant, le résultat est récuperé puis écrit dans l’input précédemment sélectionné

Création plugin partie 4 : dossier core

De loin le dossier le plus important de votre plugin, il peut comporter 4 sous dossiers.

Note : tous le long de cette partie l’id de votre plugin sera référencé par : plugin_id

PHP

Contient les fichiers PHP annexes, j’ai pris l’habitude de mettre par exemple un fichier d’inclusion si, bien sur, vous avez plusieurs fichiers de class ou des 3rparty à inclure

Template

Qui peut contenir 2 sous-dossiers, dashboard et mobile, c’est un dossier que Jeedom scanne automatiquement à la recherche de widget, donc si vous utilisez des widgets spécifiques c’est ici qu’il faut mettre leur fichier HTML

i18n

C’est ici que votre traduction doit se trouver sous forme de fichier json (le mieux et de regarder par exemple le plugin zwave pour voir la forme du fichier)

ajax

Ce dossier est pour tout vos fichiers ajax, voici un squelette de fichier ajax :

<?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());
}
?>
 

class

Dossier très important, c’est le moteur de votre plugin. C’est là que viennent les 2 classes obligatoires de votre plugin :

  • plugin\_id
  • plugin\_idCmd

La première devant hériter de la classe eqLogic et la deuxième de cmd. Voici un 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*************************** */

}

?>
 

Pour la définition des classes jeedom, je vous invite à consulter ce site

La seule méthode obligatoire est la méthode d’instance sur la classe cmd execute, voici un exemple avec le plugin S.A.R.A.H :

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();
  }
Exemple assez simple mais complet, le principe est le suivant, si la commande est une action ou une info (mais pas en événement seulement et que son cache est dépassé) alors jeedom appelle cette méthode.

Dans notre exemple ici c’est une commande pour faire parler S.A.R.A.H, où le plugin récupère les paramètres dans $_options (attention c’est un tableau et ses attributs changent en fonction du sous-type de la commande : color pour un sous-type color, slider pour un sous-type slider, title et message pour un sous-type message et vide pour un sous-type other).

Voila pour la partie obligatoire, voila maintenant ce qui peut être utilisé à coté (avec exemple) :

toHtml($_version = ‘dashboard’)

Fonction utilisable dans la commande ou dans l’équipement, en fonction des besoins, voici un exemple pour l’équipement

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;
}
 

Plusieurs choses intéressantes ici :

Pour convertir la version demandée en dashboard ou mobile (mview devient mobile par exemple, cela permet par exemple sur les vues de rajouter le nom des objets)

$_version = jeedom::versionAlias($_version);

Récupération d’un template de commande, ici le template de commande : plugins/weather/core/template/$_version/forecast.html ($_version valant mobile ou dashboard)

$forcast_template = getTemplate('core', $_version, 'forecast', 'weather');

Ici remplacement des tags préalablement remplis dans $replace du HTML pour contenir les valeurs

$html_forecast .= template_replace($replace, $forcast_template);

Cela permet de récupérer la commande ayant le logical_id : temperature_min

$this->getCmd(null, 'temperature_min');

Là cela permet de mettre la valeur dans le tag, seulement si la commande a bien été récupérée

$replace['#temperature#'] = is_object($temperature) ? $temperature->execCmd() : '';

Passage important: cela permet de récupérer les personnalisations faites par l’utilisateur sur la page Générale → Affichage et de les réinjecter dans le template

$parameters = $this->getDisplay('parameters');
if (is_array($parameters)) {
    foreach ($parameters as $key => $value) {
        $replace['#' . $key . '#'] = $value;
    }
}
 

Sauvegarde du widget dans le cache: pour que lors de la prochaine demande on le fournisse plus rapidement, on peut remarquer le 0 ici qui indique une durée de vie infinie, sinon la durée est en secondes (on verra dans la partie suivante comment le plugin weather remet à jour son widget).

cache::set('weatherWidget' . $_version . $this->getId(), $html, 0);

Enfin envoi du html à Jeedom :

return $html;

Il faut aussi dire à Jeedom ce que votre widget autorise au niveau de la personnalisation. C’est un peu complexe (et encore) mais normalement flexible et simple a mettre en place.

Il fonctionne de la même façon sur votre équipement ou commande, c’est un attribut static de la class $_widgetPossibility qui doit être un tableau multidimensionnel, mais c’est là que cela se complique si une dimension du tableau est a true ou false. Il considère alors que tout les enfants possibles sont à cette valeur (je vais donner un exemple).

En premier lieu les cas où vous devez vous en servir: si dans votre class héritant de eqLogic ou de cmd a une fonction toHtml sinon ce n’est pas la peine de lire la suite.

Méthode pre et post

Lors de la création ou la suppression de vos objets (équipement, commande ou autre) dans Jeedom, celui-ci peut appeler plusieurs méthodes avant/après l’action :

  • preInsert ⇒ Méthode appellée avant la création de votre objet
  • postInsert ⇒ Méthode appellée après la création de votre objet
  • preUpdate ⇒ Méthode appellée avant la mise à jour de votre objet
  • postUpdate ⇒ Méthode appellée après la mise à jour de votre objet
  • preSave ⇒ Méthode appellée avant la sauvegarde (creation et mise à jour donc) de votre objet
  • postSave ⇒ Méthode appellée après la sauvegarde de votre objet
  • preRemove ⇒ Méthode appellée avant la supression de votre objet
  • postRemove ⇒ Méthode appellée après la supression de votre objet

Exemple, toujours avec le plugin weather de la création des commandes ou mise à jour de celles-ci après la sauvegarde (l’exemple est simplifié) :

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();
}
 

Le début est assez standard avec la création d’une commande, la fin est plus intéressante avec la mise en place d’un cron qui va appeler la méthode weather::updateWeatherData en passant l’id de l’équipement à mettre à jour toute les 30min par défaut.

Ici la méthode updateWeatherData (simplifiée aussi) :

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());
      }
  }
}
 

On voit ici que lors de l’appel on récupère l’équipement concerné puis on exécute les commandes pour récupérer les valeurs et mettre à jour celles-ci si nécessaire.

Partie très importante :

$weather->checkAndUpdateCmd($cmd,$cmd->execute());

Au moment de la fonction checkAndUpdateCmd (qui permet de signaler à Jeedom une nouvelle mise à jour de la valeur, avec déclenchement de toutes les actions qui doivent être faites : mise à jour du dashboard, vérification des scénarios…​),

Pour la classe commande, un petit truc à savoir si vous utilisez le template js de base. Lors de l’envoi de l’équipement Jeedom fait du différentiel sur les commandes et va supprimer celles qui sont en base mais pas dans la nouvelle définition de l’équipement. Voilà comment l’éviter :

public function dontRemoveCmd() {
  return true;
}

Pour finir voici quelques trucs et astuces :

  • évitez (à moins de savoir ce que vous faites) d’écraser une méthode de la classe héritée (cela peut causer beaucoup de problèmes)
  • Pour remonter la batterie (en %) d’un équipement, faites sur celui-ci (Jeedom se chargera du reste et de prévenir l’utilisateur si nécessaire) :
$eqLogic->batteryStatus(56);
  • Sur les commandes au moment de l’ajout d’une valeur Jeedom applique la méthode d’instance formatValue($_value) qui, en fonction du sous-type, peut la remettre en forme (en particulier pour les valeurs binaires)
  • ne faites JAMAIS une méthode dans la class héritant de cmd s’appelant : execCmd ou event
  • si dans la configuration de votre commande vous avez renseigné returnStateTime (en minute) et returnStateValue, Jeedom changera automatique la valeur de votre commande par returnStateValue au bout de X minute(s)
  • toujours pour la commande vous pouvez utiliser addHistoryValue pour forcer la mise en historique (attention votre commande doit être historisée)
Featured

Comparaison entre Home Assistant et Jeedom : quel système domotique choisir ?

La domotique est en plein essor, et de nombreuses solutions sont disponibles pour transformer nos maisons en habitations intelligentes. Parmi les plateformes les plus populaires, Home Assistant et Jeedom se distinguent par leur flexibilité et leur richesse fonctionnelle. Toutefois, bien que ces deux systèmes aient de nombreux points communs, ils présentent aussi des différences significatives qui peuvent orienter votre choix. Cet article compare ces deux solutions pour vous aider à choisir celle qui conviendra le mieux à vos besoins.

Information

Ce site est construit autour de Joomla 5, en utilisant le template Helix Ultimate et les composants : SP Builder Pro pour la construction de certaines pages, Smart Slider 3 Free pour les slideshows, Komento Free pour les commentaires sur les articles et Ignite Gallery pour les galeries de photos.

Mentions légales

Le site est édité par Christian Sammut
130, Avenue du Général Leclerc
45800 Saint Jean de Braye
Tél.: 06.01.74.90.33
E-mail: contact@sammut.fr

Ce site est hébergé par AMEN SASU
12-14, Rond Point des Champs Elysées
75008 Paris