Frontend PHP pour l’écoute du bus KNX

Dans un précédent article, j’ai décris comment écouter le bus KNX pour allumer une rampe de LED via Open DMX.
En fin d’article, j’ai abordé l’aspect possible d’un frontend en PHP pour avoir une base de données MySQL à jour, en fonction des évènements du KNX.

Utilisation de groupsocketlisten pour une interface web

Utilisation de groupsocketlisten pour une interface web

L’intérêt est quasi le même que linknx, un changement sur le bus est égal à un changement en BDD sur les Groupes d’Adresses qui m’intéressent.
Le but est de pouvoir exploiter ces données sur une page web et d’autres scripts maisons sans faire de requête sur le bus, mais en interrogeant ma BDD.
Une espèce de travail en couche d’applications.

Pour rappel, je n’ai rien contre linknx, j’aurais juste du mal à adapter mon cahier des charges à cet outil, voir mon article :
Se passer de linknx et webknx2, une fausse bonne idée ?

Pour ce front-end, je me suis inspiré de mon premier script avec logtail.
En cherchant des solutions, je suis tombé sur l’extention php inotify et son tail (en commentaire).

La base

Pour commencer, toujours depuis Debian Wheezy, il faut posséder php5, php-pear ainsi qu’une base MySQL évidemment.
inotify s’installe comme ceci au plus simple :

# pecl install --force inotify

L’extention activée pour php5 pour le cli :

# echo "extension=inotify.so" > /etc/php5/cli/conf.d/inotify.ini

Selon mon premier script pour allumer ma rampe LED, re-modification de mon script d’init.d eibnetmux.
A la différence, que je log tout ce qu’il se passe sur le bus.

# Function that starts the daemon/service
#
do_start()
{
echo -n "Starting eibdnetmux"
/usr/local/bin/eibnetmux $DAEMON_ARGS
echo " done"
sleep 2
echo -n "Starting group listen"
/usr/local/bin/groupsocketlisten ip:127.0.0.1 > /tmp/knx_groupsocketlisten &
echo " done"
}

Mon fichier « log » du bus est /tmp/knx_groupsocketlisten

J’ai crée, ce qui est pour l’instant une simple table avec les Groupe Addresses qui m’intéressent, à l’éfigie de linknx :

--
-- Structure de la table `knx_ga`
--

CREATE TABLE `knx_ga` (
  `knx_ga_id` int(3) NOT NULL AUTO_INCREMENT,
  `knx_ga_groupaddress` tinytext NOT NULL,
  `knx_ga_value` text NOT NULL,
  `knx_ga_comment` text NOT NULL,
  PRIMARY KEY (`knx_ga_id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1;

--
-- Contenu de la table `knx_ga`
--

INSERT INTO `knx_ga` (`knx_ga_id`, `knx_ga_groupaddress`, `knx_ga_value`, `knx_ga_comment`) VALUES
(1, '0/0/1', '1', 'Etat de la lumiere'),
(2, '0/0/2', '10', 'Valeur d''éclairement'),
(3, '0/0/10', '0', 'DMX');

J’ai 3 groupes d’adresses, ça n’a pas de sens sans une mise en situation.

Mise en situation

J’ai un Hager TXA213 (3 sorties variation 300W), un Hager WKT302 (poussoir 2 boutons), une lampe à la sortie 1 du TXA213.
Une simple pression sur le bouton 1 allume ma lampe, une pression continue augmente la luminosité ; une simple pression sur le bouton 2 éteind la lampe, une pression continue diminue la luminosité.

Pour le détail, voici un export de mes Groupes d’Adresses :
Groupes d’adresses pour tests (pdf)

J’ai trois groupes parmis les sept qui m’intéressent :
0/0/1 → Indication d’état.
0/0/2 → Valeur d’éclairement & Indication valeur d’éclairement.
0/0/10 → Allumage DMX.

Le script

Voici la formule magique, à dérouler, parce que c’est vraiment long.

<!--?php $file = "/tmp/knx_groupsocketlisten"; /**  * Tail a file (UNIX only!)  * Watch a file for changes using inotify and return the changed data  *  * @param string $file - filename of the file to be watched  * @param integer $pos - actual position in the file  * @return string  */ function tail($file,&$pos) {     // get the size of the file     if(!$pos) $pos = filesize($file);     // Open an inotify instance     $fd = inotify_init();     // Watch $file for changes.     $watch_descriptor = inotify_add_watch($fd, $file, IN_ALL_EVENTS);     // Loop forever (breaks are below)     while (true) {         // Read events (inotify_read is blocking!)         $events = inotify_read($fd);         // Loop though the events which occured         foreach ($events as $event=-->$evdetails) {
            // React on the event type
            switch (true) {
                // File was modified
                case ($evdetails['mask'] & IN_MODIFY):
                    // Stop watching $file for changes
                    inotify_rm_watch($fd, $watch_descriptor);
                    // Close the inotify instance
                    fclose($fd);
                    // open the file
                    $fp = fopen($file,'r');
                    if (!$fp) return false;
                    // seek to the last EOF position
                    fseek($fp,$pos);
                    // read until EOF
                    while (!feof($fp)) {
                        $buf .= fread($fp,8192);
                    }
                    // save the new EOF to $pos
                    $pos = ftell($fp); // (remember: $pos is called by reference)
                    // close the file pointer
                    fclose($fp);
                    // return the new data and leave the function
                    return $buf;
                    // be a nice guy and program good code ;-)
                    break;

                    // File was moved or deleted
                case ($evdetails['mask'] & IN_MOVE):
                case ($evdetails['mask'] & IN_MOVE_SELF):
                case ($evdetails['mask'] & IN_DELETE):
                case ($evdetails['mask'] & IN_DELETE_SELF):
                    // Stop watching $file for changes
                    inotify_rm_watch($fd, $watch_descriptor);
                    // Close the inotify instance
                    fclose($fd);
                    // Return a failure
                    return false;
                    break;
            }
        }
    }
}

// Conection avec la base de données mysql
$db = mysql_connect("127.0.0.1", "root", "votremotdepassemysql");
$mysql=mysql_select_db("votrebase",$db);

$req_sel_ga     = "SELECT knx_ga_id,knx_ga_groupaddress FROM knx_ga";
$qur_sel_ga     = mysql_query($req_sel_ga) or die (mysql_error());
$ga_bdd         = array();
while($dat_sel_ga = mysql_fetch_array($qur_sel_ga)) {
        $knx_ga_id              = $dat_sel_ga['knx_ga_id'];
        $knx_ga_groupaddress    = $dat_sel_ga['knx_ga_groupaddress'];
        $ga_bdd[$knx_ga_groupaddress]   = $knx_ga_id;
}

// Use it like that:
$lastpos = 0;
while (true) {
        // On tail le fichier de log
        $knxlisten = tail($file,$lastpos);
        //echo $knxlisten;
        // On réagit dès qu'on a un Write
        if (preg_match('#^Write#',$knxlisten)) {
                // Recup du Groupe d'Addresse et de la valeur qu'on converti
                preg_match('#[0-9]*\/[0-9]*\/[0-9]*#',$knxlisten,$groupaddr);
                preg_match("#[A-F0-9]*(.)$#",$knxlisten,$value);
                $value          = hexdec($value[0]);
                $groupaddr      = $groupaddr[0];
                // on regarde si ca fait parti des elements à surveiller
                if (array_key_exists($groupaddr, $ga_bdd)) {
                        // la valeur associé = l'id dans la BDD
                        $knx_ga_id_update       = $ga_bdd[$groupaddr];
                        $req_sel_ga_update      = "UPDATE knx_ga SET knx_ga_value = '$value' WHERE knx_ga_id = $knx_ga_id_update";
                        // Et on update la BDD !
                        mysql_query($req_sel_ga_update);
                        if ($knx_ga_id_update == "0/0/10" && $value == "01") exec("dmxchangecolorfader.py 255 0 255");
                        if ($knx_ga_id_update == "0/0/10" && $value == "00") exec("dmxchangecolor.py 0 0 0");
                }
        }
}

Abracadabra !
C’est posté sans mise au propre, mon dernier partage de script PHP était pour le 1-wire, il a fallu faire une archive pour un script plus simple, le voici brut, à personnaliser, poser des includes, variables de configuration, etc…

En gros, il fait quoi ce script ?

Il récupère les groupes d’adresses à surveiller dans la BDD.
Il fait un tail sur notre fichier de sortie groupsocketlisten.
Pour chaque nouvel évènement « Write », il regarde si le groupe d’adresse fait parti de ceux dans la BDD.
Si c’est le cas, il y écrit la valeur.

Cas pratique :

Si j’allume ma lumière, 0/0/1 passe à 1 sur mon bus.
groupsocketlisten l’écris dans le fichier /tmp/knx_groupsocketlisten
Ce script voit passer 0/0/1, il sait que sa valeur m’intéresse, il y écrit immédiatement la valeur dans la base de donnée.

Il suffit de lancer ce script par le cron, à la main (en ligne de commande uniquement), par un init.d ou autre.

La mise à jour de la base est quasi instantanée !
Le fonctionnement du script ci-dessous charge les GA de la base MySQL et les place dans une variable puis les compare à GA qui circule sur le bus.

A la fin du script, la partie liée à 0/0/10 est pour reprendre le fonctionnement de l’article de base et l’allumage du DMX.
Je songe à  l’intégrer différemment, très certainement par l’ajout d’un champ contenant la spécificité et le script à exécuter.

Résultat du script via phpmyadmin

Résultat du script via phpmyadmin

Et voila ! Ou comment re-inventer la roue : linknx !

Il me reste à personnaliser ce script via des includes, des paramètres, rajouter un champs dans la table pour appeler un GA par un nom, un autre champs IES pour le write, et faire des essais sur une page web.

Ce billet est posté dans Debian, php, Sysadmin, Web. Mettre en favoris le permalien.

Une réponse à Frontend PHP pour l’écoute du bus KNX

  1. Pingback: Interagir en jQuery/Ajax avec le bus KNX | Domolio, la domotique et pas que…

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *