Une interface web pour contrôler le bus KNX sur Raspberry Pi

Raspberry Pi logo

Raspberry Pi logo

Pour reprendre mes différents articles sur le bus KNX, j’ai décidé de passer mon projet sur Raspberry pour vérifier que mes lignes de commandes sont toujours d’actualitées, effectuer un point sur ce que j’ai fait, pouvoir partager le schmilblick.

En plus d’être à la mode, le Raspberry Pi a un très belle avenir dans la domotique !

Comme ce blog est rédigé au fil de mes découvertes du KNX, j’ai essayé de synthétiser mon article sur la prise de contrôle du bus via jQuery.
A savoir que ce qui suit peut également être refait sur du i386…

Objectif

Re-partir de zéro et d’arriver à une interface web permettant d’allumer/éteindre une lumière et de régler sa luminosité (TXA213).
Pouvoir lire et écrire à tout va sur le bus KNX ; l’état des élèments soit lisible et modifiable, un max de souplesse !

La base

Dans un premier temps, je pars sur une base de Raspbian wheezy (sans démarrage de X11) – de très bon tutoriaux existent sur la toile – puis c’est parti pour l’installation d’eibnetmux sur Raspberry.
Pourquoi eibnetmux et pas eibd ? Parce qu’au fur et à mesure de l’utilisation je trouve personnellement qu’eibnetmux est plus simple d’utilisation, plus complet, mieux fini.

# sudo passwd
# su
# apt-get update
# apt-get dist-upgrade
# apt-get install build-essential

Coté Raspberry Pi, je fais tout en root, ajoutez vos outils préférés, nano, vim, alias, etc…

Coté KNX je dispose simplement d’un module HAGER TXA213 : 3 sorties variables 300W
Deux groupes d’adresses :
0/0/1 : sortie 1 → On/Off + indication d’état
0/0/2 : sortie 1 → Valeur d’éclairement + indicateur valeur d’éclairement.

Note : C’est un exemple de groupe d’adresses, apriori, j’ai lu qu’il est préférable de séparer l’indication d’état de  la commande on/off, sans savoir pour quelle raison.

Ainsi que du classique : une alimentation KNX, un routeur IP/KNX et son alimentation dédiée.

Schéma de base KNX

Schéma de base KNX

Téléchargement et installation de Zlogger

Quelques libraires nécessaires pour l’installation :

# apt-get install libpth20 libpth-dev libpolarssl0 pkg-config pth-dbg

Zlogger 1.5.0 est nécessaire au fonctionnement d’eibnetmux.

# cd /usr/src/
# wget "http://downloads.sourceforge.net/project/zlogger/zlogger/1.5.0/zlogger-1.5.0.tar.bz2?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fzlogger%2F&ts=1334692716&use_mirror=freefr" -O zlogger-1.5.0.tar.bz2
# tar xfv zlogger-1.5.0.tar.bz2
# cd zlogger-1.5.0
# ./configure --with-plugins --enable-pth-plugins
# make
# make install
# ldconfig

Télechargement et installation d’eibnetmux

Plus de détails dans l’article dédié à eibnetmux et eibd.
La version d’eibnetmux est la dernière disponible selon le site de son auteur via sourceforge.

Pour utliser eibnetmux avec php, celui-ci est nécessaire*.

# apt-get install php5 php5-dev php-pear

* Merci à jjay pour ces infos en commentaires

Puis on installe eibnetmux

# cd /usr/src/
# wget "http://downloads.sourceforge.net/project/eibnetmux/eibnetmux/2.0.1/eibnetmux-2.0.1.tar.gz?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Feibnetmux%2Ffiles%2Feibnetmux%2F2.0.1%2F&ts=1334739400&use_mirror=netcologne" -O eibnetmux-2.0.1.tar.gz
# tar zxvf eibnetmux-2.0.1.tar.gz
# cd eibnetmux-2.0.1
# ./configure --enable-php
# make
# make install
# ldconfig

On récupère et installe le « Sample client applications » ; ce package contient les fichiers PHP nécessaires pour un lire et écrire sur le bus KNX..

Il contient également les commandes de bases équivalentes à groupread, groupwrite, groupswrite, groupsocketlisten, etc…

# cd /usr/src/
# wget "http://downloads.sourceforge.net/project/eibnetmux/Sample%20client%20applications/1.7.1/eibnetmuxclientsamples-1.7.1.tar.gz?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Feibnetmux%2Ffiles%2FSample%2520client%2520applications%2F1.7.1%2F&ts=1361557458&use_mirror=freefr" -O eibnetmuxclientsamples-1.7.1.tar
# tar xvf eibnetmuxclientsamples-1.7.1.tar
# cd eibnetmuxclientsamples-1.7.1

Pause !
Une grosse modification apportée à un fichier, il s’agit du fichier eibtrace/eibtrace.c :
J’ai modifié le fichier pour retourner à une syntaxe plus simple à traiter :

eibtrace.c

J’ai également ajouté un « fflush(stdout);« , sans cet ajout, eibtrace ne parvient pas à rediriger stdout vers un fichier. Et comme le C++ et moi, ça fait deux, je me contente de cette rustine…

Et on compile les scripts d’exemples d’eibnetmux.

# ./configure
# make
# make install

Le petit souci étant que je n’ai pas trouvé l’astuce pour compiler directement dans /usr/local/bin ou équivalent.
Alors en attendant …

# cp eibcommand/eibcommand /usr/local/bin/
# cp eibmultiread/eibmultiread /usr/local/bin/
# cp eibread/eibread /usr/local/bin/
# cp eibstatus/eibstatus /usr/local/bin/
# cp eibtrace/eibtrace /usr/local/bin/
# cp firereads/firereads /usr/local/bin/
# cp readmemory/readmemory /usr/local/bin/
# cp resetdevice/resetdevice /usr/local/bin/
# cp search/search /usr/local/bin/
# cp writememory/writememory /usr/local/bin/

eibtrace est l’équivalent de groupsocketlisten d’eibd.
eibcommand est l’équivalent groupwrite d’eibd.
eibread est l’équivalent groupread d’eibd.
A noter que groupread, groupwrite, groupswrite, groupsocketlisten et compagnie sont compatible avec eibnetmux.

La librairie PHP se trouve /usr/src/eibnetmux-2.0.1/client_lib/php/eibnetmux.php et les commandes getstatus.php readgroup.php writegroup.php dans /usr/src/eibnetmuxclientsamples-1.7.1/php , je me garde ça sous le coude pour l’instant.

Test intermédiaire de lecture du bus KNX

Il est temps de tester eibnetmux, et ses commandes.
L’IP de mon interface IP/KNX est 192.168.0.51.

# eibnetmux -s -t -u -e -d --pidfile=/var/run/eibnetmux.pid 192.168.0.51 
# ps aux | grep eibnetmux
root 1993 0.0 0.0 3152 912 ? Ss 17:02 0:00 eibnetmux -s -t -u -e -d --pidfile=/var/run/eibnetmux.pid 192.168.0.51
root 2067 0.0 0.0 4060 820 pts/0 S+ 17:20 0:00 grep eibnetmux

Tâchons d’écouter ce qu’il se passe sur le bus :

# eibtrace 127.0.0.1
Connection to eibnetmux '127.0.0.1' established

Puis dans un autre shell, allumons et éteignons la lumière (groupe d’adresse 0/0/1 comme indiqué au début) :

# eibcommand -s 127.0.0.1 0/0/1 1 1
# eibcommand -s 127.0.0.1 0/0/1 1 0

Ce qui renvoit dans notre eibtrace :

2013/04/18 22:38:45:640 – phys addr: 0.2.189 – IND low W group addr: 0/0/1 – value: 1 | 1 | 1 | 1 (81 – eis types: 1, 2, 7, 8)
2013/04/18 22:38:45:686 – phys addr: 1.1.2 – IND low W group addr: 0/0/1 – value: 1 | 1 | 1 | 1 (81 – eis types: 1, 2, 7, 8)
2013/04/18 22:38:45:741 – phys addr: 1.1.2 – IND low W group addr: 0/0/2 – value: 65 | 168 (a8 – eis types: 6, 14)

Puis

2013/04/18 22:38:54:278 – phys addr: 0.2.189 – IND low W group addr: 0/0/1 – value: 0 | 0 | 0 | 0 (80 – eis types: 1, 2, 7, 8)
2013/04/18 22:38:54:366 – phys addr: 1.1.2 – IND low W group addr: 0/0/1 – value: 0 | 0 | 0 | 0 (80 – eis types: 1, 2, 7, 8)
2013/04/18 22:38:54:432 – phys addr: 1.1.2 – IND low W group addr: 0/0/2 – value: 0 | 0 (00 – eis types: 6, 14)

A noter, que sans modifier le ficher eibtrace.c avant sa compilation, nous aurions eu ceci :

2013/04/14 17:28:43:570 – 0.2.189 IND low W 0/0/1 : on | 1 | 1 | 1 (81 – eis types: 1, 2, 7, 8)
2013/04/14 17:28:43:614 – 1.1.2 IND low W 0/0/1 : on | 1 | 1 | 1 (81 – eis types: 1, 2, 7, 8)
2013/04/14 17:28:43:671 – 1.1.2 IND low W 0/0/2 : 65% | 168 (a8 – eis types: 6, 14)

Puis

2013/04/14 17:29:07:316 – 0.2.189 IND low W 0/0/1 : off | 0 | 0 | 0 (80 – eis types: 1, 2, 7, 8)
2013/04/14 17:29:07:403 – 1.1.2 IND low W 0/0/1 : off | 0 | 0 | 0 (80 – eis types: 1, 2, 7, 8)
2013/04/14 17:29:07:480 – 1.1.2 IND low W 0/0/2 : 0% | 0 (00 – eis types: 6, 14)

Pensez à faire un killall eibnetmux.
Bravo, on vient de faire un groupswrite : eibcommand -s <serveur> <group address> <type eis> <valeur>

Avec eibtrace, on trace les valeurs lues sur le bus KNX, lampe allumée, variateur à 65%, puis lampe éteinte, variateur à 0%.
0.2.189 correspond à ma passerelle IP/KNX, 1.1.2 à mon TXA213.
Si l’ordre provient d’un interrupteur, le phénomène est identique.

Ecoute du bus avec eibtrace

Ecoute du bus avec eibtrace

La suite consiste à avaler puis digérer ces valeurs.

Absorbtion des valeurs du bux KNX

J’ai besoin de php5 + mysql-server et tant qu’à faire, apache 2 pour la visualisation des lumières :

# apt-get install php5 php5-cli php5-mysql libapache2-mod-php5 php5-dev apache2 mysql-server php-pear

Normalement, une partie de ces paquets devraient déjà être installé avec l’installation d’eibnetmux.

Mon mot de passe mysql pour suivre cet article : dbpass

Pour les plus « doués », je conseille quelque chose de plus léger qu’Apache2 sur Raspberry Pi, genre Lighttpd, Cherokee, Hiawatha.

Le script d’ init.d pour eibnetmux s’illustre d’une particularité à la ligne 41, en plus de démarrer eibnetmux sur mon interface IP Siemens N146 5WG1 146-1AB01 (192.168.0.51), il lance en fond de tâche un « eibtrace 127.0.0.1 » vers le fichier /tmp/eibtrace . Tout ce qui se passera à partir de l’exécution d’eibnetmux sera loggé dans le fichier /tmp/eibtrace .
Note : sans l’ajout de « fflush(stdout); » sans eibtrace.c, la redirection stdout ne fonctionne pas.

# vi /etc/init.d/eibnetmux

Qui contient :

#! /bin/sh
### BEGIN INIT INFO
# Provides: eibnetmux
# Required-Start: mysql
# Required-Stop: mysql
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Start eibnetmux service at the end of boot
# Description: This services is for communications with knx/eib.
### END INIT INFO

# by Lionel @ domolio.fr

PATH=/sbin:/usr/sbin:/bin:/usr/bin
NAME=eibnetmux
DAEMON=/usr/local/bin/eibnetmux
DAEMON_ARGS="-s -t -u -e -d --pidfile=/var/run/eibnetmux.pid 192.168.0.51"
PIDFILE=/var/run/eibnetmux.pid

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions

if [ ! -x $DAEMON ]; then
 echo "eibnetmux appears to be uninstalled."
fi

#
# Function that starts the daemon/service
#
do_start()
{
echo -n "Starting eibnetmux"
/usr/local/bin/eibnetmux $DAEMON_ARGS
echo " done"
sleep 2
echo -n "Starting eibtrace"
/usr/local/bin/eibtrace 127.0.0.1 > /tmp/eibtrace &
echo " done"
}

do_stop()
{
echo -n "Stoping eibnetmux"
PID=`cat $PIDFILE`
kill $PID &> /dev/null
echo " done"
}

#
# Function that stops the daemon/service
#

case "$1" in
 start)
 do_start
 ;;
 stop)
 do_stop
 ;;
 restart)
 do_stop
 sleep 1
 do_start
 ;;
 status)
 pid=$(pidofproc -p $PIDFILE $DAEMON)
 ret=$?
 pid=${pid% } # pidofproc() supplies a trailing space, strip it

if [ $ret -eq 0 ]; then
 echo "eibnetmux is running (PID: $pid)"
 exit 0
 elif [ $ret -eq 1 ] || [ $ret -eq 2 ]; then
 echo "eibnetmux is dead, although $PIDFILE exists."
 exit 1
 elif [ $ret -eq 3 ]; then
 echo "eibnetmux is not running."
 exit 3
 fi
 echo "sais pas"
 ;;
 *)
 echo "usage: $0 {start|stop|restart|status}"
 exit 1
esac
# chmod +x /etc/init.d/eibnetmux
# insserv eibnetmux

A ce stade, j’ai un raspberry capable d’écouter et de commander mon bus KNX, je vais y rajouter ma sauce « home made » pour lire et écrire comme je l’entends.
Je le fais en PHP, parce que c’est un des seuls languages que je connais 🙂
J’installe le paquet php-pear afin d’installer System_Daemon pour me permettre de daemoniser un script PHP et d’avoir des logs.

# pear install -f System_Daemon

Lire le très bon article de Kevin van Zonneveld : Create daemon in PHP.

Le principe

Une fonction forte utile : tail ; bien connue des utilisateurs UNIX, mais pour l’utiliser en PHP, j’utilise le script dispo sur le book php : http://php.net/manual/fr/function.inotify-init.php
Comme signalé dans le premier commentaire, le résultat est identique à un « tail -f » exécuté dans un shell.
J’avais déjà utilisé la commande unix logtail pour arriver à mes fins dans un script permettant de commander un bandeau LED RGB via KNX, toujours en exploitant les logs.
Communication entre un bus KNX et un serveur linux, avec cette fonction PHP, c’est plus propre.

Ces deux outils vont me permettre de faire de mon script un vrai « service » sur mon Rapsberry Pi.
Il va me permettre d’avoir des logs, un service propre à la communication KNX ↔ Raspberry Pi.

Le but étant d’écouter tout ce qui se passe sur le bus (via eibtrace) et de l’écrire dans un fichier.

Eibtrace log dans un fichier

Eibtrace log dans un fichier

Puis, de « parser » ce fichier pour que chaque action qui nous intéresse soit retranscrite dans une table MySQL.
Vient enfin une page qui consulte cette base MySQL, donc : l’état de mes intervenants KNX.

Nous devons installer l’extention php inotify, puis l’activer :

# pecl install inotify
# vi /etc/php5/conf.d/30-inotify.ini

Pour y insérer :

extension=inotify.so

Mise en place

Structure de la base de données

Pourquoi faire simple quand on peut faire compliquer ?
Par souplesse, je split mes données en 2 tables :
– La première regroupe les Group Address → knx_ga .
– La secondes regroupe les EIS possible → knx_eis.

Ma base s’appelle domy.
Un mysqldump :

--
-- Table structure for table `knx_ga`
--

DROP TABLE IF EXISTS `knx_ga`;
CREATE TABLE `knx_ga` (
  `ga_id` int(3) NOT NULL AUTO_INCREMENT,
  `ga_groupaddress` tinytext NOT NULL,
  `ga_eis_id` int(3) NOT NULL,
  `ga_name` mediumtext NOT NULL,
  `ga_value` text NOT NULL,
  `ga_comment` text NOT NULL,
  PRIMARY KEY (`ga_id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;

--
-- Table structure for table `knx_eis`
--

DROP TABLE IF EXISTS `knx_eis`;
CREATE TABLE `knx_eis` (
  `eis_id` int(3) NOT NULL AUTO_INCREMENT,
  `eis_code` tinytext NOT NULL,
  `eis_num` int(3) NOT NULL,
  `eis_type` tinytext NOT NULL,
  PRIMARY KEY (`eis_id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
--
-- Dumping data for table `knx_eis`
--

LOCK TABLES `knx_eis` WRITE;
/*!40000 ALTER TABLE `knx_eis` DISABLE KEYS */;
INSERT INTO `knx_eis` VALUES (1,'1.001',1,'Off / Off'),(2,'5.001',2,'Pourcentage');
/*!40000 ALTER TABLE `knx_eis` ENABLE KEYS */;
UNLOCK TABLES;

La table knx_eis contient le numéro de type d’eis, le type de datapoint, et un petit commentaire.
Une fois importé, je renseigne mon premier Group Address du bux KNX, le 0/0/1, correspondant au On/Off + indicateur d’état.

INSERT INTO `domy`.`knx_ga` (`ga_id`, `ga_groupaddress`, `ga_eis_id`, `ga_name`, `ga_value`, `ga_comment`) VALUES (NULL, '0/0/1', '1', 'lampe1_onoff', '0', 'Lampe 1');

Idem avec mon GA 0/0/2, correspondant à la valeur d’éclairage, pour rappel, il s’agit d’un variateur TXA213.

INSERT INTO `domy`.`knx_ga` (`ga_id`, `ga_groupaddress`, `ga_eis_id`, `ga_name`, `ga_value`, `ga_comment`) VALUES (NULL, '0/0/2', '2', 'lampe1_eclairage', '0', 'Lampe 1 : valeur éclairement');

Le champs de la table qui va changer, c’est ga_value, il passera à 1 lorsque la lumière est allumée, à 0 lorsqu’elle est éteinte.
Pour l’éclairage, une valeur entre 0 et 100.
Et voila, les racines sont là, il reste à remplir ces données et les exploiter.

knx-tail2mysql : parcourir l’activité du bus KNX vers table MySQL

Vous mélangez le script System_Daemon + Log Tail + un peu de MySQL pour obtenir ce script (pensez à cliquer pour tout dérouler)

# touch /usr/local/bin/knx-tail2mysql
# chmod +x /usr/local/bin/knx-tail2mysql
# vi /usr/local/bin/knx-tail2mysql

Script qui contient :

#!/usr/bin/php -q
<?php
// --------------------------------------
// --       Daemonise PHP script       --
// --------------------------------------
// D'après http://kvz.io/blog/2009/01/09/create-daemons-in-php/
// Allowed arguments & their defaults
 
$runmode = array(
    'no-daemon' => false,
    'help' => false,
    'write-initd' => false,
);
 
// Scan command line attributes for allowed arguments
foreach ($argv as $k=>$arg) {
    if (substr($arg, 0, 2) == '--' && isset($runmode[substr($arg, 2)])) {
        $runmode[substr($arg, 2)] = true;
    }
}

// Help mode. Shows allowed argumentents and quit directly
if ($runmode['help'] == true) {
    echo 'Usage: '.$argv[0].' [runmode]' . "\n";
    echo 'Available runmodes:' . "\n";
    foreach ($runmode as $runmod=>$val) {
        echo ' --'.$runmod . "\n";
    }
    die();
}

// Make it possible to test in source directory
// This is for PEAR developers only
ini_set('include_path', ini_get('include_path').':..');

// Include Class
error_reporting(E_STRICT);
require_once 'System/Daemon.php';

// Setup
$options = array(
    'appName' => 'knx-tail2mysql',
    'appDir' => dirname(__FILE__),
    'appDescription' => 'Parses KNX logfiles and stores them in MySQL',
    'authorName' => 'Lionel',
    'authorEmail' => 'lionel@chocolio.com',
    'sysMaxExecutionTime' => '0',
    'sysMaxInputTime' => '0',
    'sysMemoryLimit' => '1024M',
    'appRunAsGID' => 0,
    'appRunAsUID' => 0,
);

System_Daemon::setOptions($options);

// This program can also be run in the forground with runmode --no-daemon
if (!$runmode['no-daemon']) {
    // Spawn Daemon
    System_Daemon::start();
}

// With the runmode --write-initd, this program can automatically write a
// system startup file called: 'init.d'
// This will make sure your daemon will be started on reboot
if (!$runmode['write-initd']) {
    System_Daemon::info('not writing an init.d script this time');
} else {
    if (($initd_location = System_Daemon::writeAutoRun()) === false) {
        System_Daemon::notice('unable to write init.d script');
    } else {
        System_Daemon::info(
            'sucessfully written startup script: %s',
            $initd_location
        );
    }
}

// --------------------------------------
// --            function tail         --
// --------------------------------------

// D'après http://php.net/manual/fr/function.inotify-init.php

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 <img class="wp-smiley" alt=";-)" src="https://www.domolio.fr/wp-includes/images/smilies/icon_wink.gif" />
                    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;
            }
        }
    }
}

// ------------------------------------------
// --      MON CODE PERSO COMMENCE ICI     --
// ------------------------------------------

// Connexion avec la base de données mysql
// Egalement possible de faire un include
$MYSQLHOST      = "localhost";
$MYSQLLOGIN     = "root";
$MYSQLPWD       = "dbpass";
$MYSQLBASE      = "domy";
$db = mysql_connect($MYSQLHOST, $MYSQLLOGIN, $MYSQLPWD);
$mysql=mysql_select_db($MYSQLBASE,$db);

if ($mysql!=1) {
  System_Daemon::notice('La connection avec la base de données a échoué.');
  exit();
}

// ---
// D'après http://stackoverflow.com/questions/9668299/extract-part-of-string-with-php
function get_string_between($string, $start, $end){
    $string = " ".$string;
    $ini = strpos($string,$start);
    if ($ini == 0) return "";
    $ini += strlen($start);
    $len = strpos($string,$end,$ini) - $ini;
    return trim(substr($string,$ini,$len));
}

// ---
// Quels sont les GA à surveiller ?

$req_sel_ga     = "SELECT ga_id,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)) {
        $ga_id          = $dat_sel_ga['ga_id'];
        $ga_groupaddress        = $dat_sel_ga['ga_groupaddress'];
        $ga_bdd[$ga_groupaddress]       = $ga_id;
}
// ---

// ---
// parse/tail du fichier du eibtrace
$lastpos = 0;
while (true) {
	// On tail le fichier de log
	$knxlisten = tail("/tmp/eibtrace",$lastpos);
	// On réagit dès qu'on a un Write
	// Pour chaque ligne, on récupère le Groupe d'Addresse et de la valeur qu'on converti
	System_Daemon::notice($knxlisten);
	$groupaddr	= get_string_between($knxlisten,'group addr: ',' -');
	$value		= get_string_between($knxlisten,'value: ',' |');
	System_Daemon::notice("GA : ".$groupaddr." | VALUE : ".$value);
	// on regarde si ca fait partit des elements à surveiller
	if (array_key_exists($groupaddr, $ga_bdd)) {
                        // la valeur associé = l'id dans la BDD
                        $ga_id_update   = $ga_bdd[$groupaddr];
                        //System_Daemon::info($groupaddr." est à surveiller. Mise à jour de sa valeur dans la BDD");
                        System_Daemon::info($groupaddr." -> ".$value);
                        $req_sel_ga_update      = "UPDATE knx_ga SET ga_value = '$value' WHERE ga_id = $ga_id_update";
                        //System_Daemon::info("Requete SQL -> ".$req_sel_ga_update);
                        // Et on update la BDD
                        mysql_query($req_sel_ga_update);
                }
}

?>

Ici, les informations de connexion à la base MySQL sont en dur, à partir de la ligne 143.
On notera le chemin du fichier à parser à la ligne 186.

Egalement ici, on se rend compte de modifier eibtrace, par simplicité, en effet, une ligne de log se présente ainsi :
2013/04/18 22:38:45:741 – phys addr:    1.1.2 – IND low    W group addr:    0/0/2 value: 65 | 168 (a8  – eis types: 6, 14)
Le script récupère ce qu’il y’a entre « group addr » et « « , puis entre « value: » et « |« . Plus simple …

Un petit test … Il est nécessaire d’avoir le script init.d/eibnetmux de lancé avec eibtrace qui redirige vers /tmp/eibtrace.

# knx-tail2mysql
[Apr 14 22:53:22] notice: Starting knx-tail2mysql daemon, output in: '/var/log/knx-tail2mysql.log'
# cat /var/log/knx-tail2mysql.log
[Apr 14 22:53:22] notice: Starting knx-tail2mysql daemon, output in: '/var/log/knx-tail2mysql.log'
[Apr 14 22:53:22] info: Changed identify to 'root':'root'
[Apr 14 22:53:22] info: not writing an init.d script this time

Tout va bien … Tuez le processus si il est en fond de tâche.

Script de démarrage

Le script se lance en tant que daemon, pour ne pas oublier, faisons un petit init.d script et le logrotate:

# knx-tail2mysql --write-initd

Et il vous pond un script init.d ! magique !

# vi /etc/init.d/knx-tail2mysql

Une petite modification malgré tout, afin de s’assurer que le parse du fichier de traces se lance après que la connexion au bus soit effective, et donc fichier crée.
Sans quoi, il se lance avant même que mysql ne soit lancé…

# Required-Start:    $remote_fs
# Required-Stop:     $remote_fs

Deviens :

# Required-Start:    eibnetmux
# Required-Stop:     eibnetmux

Et on l’installe en tant que script de démarrage :

# insserv knx-tail2mysql

Logrotate

Coté logrotate, l’on crée un script dans le dossier /etc/logrotate.d/ qui sera automatiquement traité quotidiennement, généralement à 06h25.

# vi /etc/logrotate.d/knx-tail2mysql

Contient :

/var/log/knx-tail2mysql.log {
        weekly
        missingok
        rotate 12
        compress
        delaycompress
        notifempty
        sharedscripts
        postrotate
                /etc/init.d/knx-tail2mysql restart > /dev/null
        endscript
}
# ls -lh /var/log/knx-tail2mysql.log*
-rw-r--r-- 1 root root 449 févr. 21 00:20 /var/log/knx-tail2mysql.log
# logrotate -f /etc/logrotate.d/knx-tail2mysql
# ls -lh /var/log/knx-tail2mysql.log*
-rw-r--r-- 1 root root 306 févr. 21 00:21 /var/log/knx-tail2mysql.log
-rw-r--r-- 1 root root 449 févr. 21 00:20 /var/log/knx-tail2mysql.log.1

L’on passe d’un fichier à deux fichiers, les suivants seront compressés, ce logrotate fonctionne.

Essai d’exécution

Eibtrace doit être en route en fond de tâche (les scripts précédents le lance en même temps qu ‘eibnetmux), avec écriture de la trace dans le fichier /tmp/eibtrace .
Un petit init 6 c’est bien aussi.
On lance knx-tail2mysql et on vérifie qu’un processus tourne en fond de tâche :

# knx-tail2mysql
[Apr 14 23:33:27] notice: Starting knx-tail2mysql daemon, output in: '/var/log/knx-tail2mysql.log'
# ps aux | grep knx-tail2mysql
root 3163 0.2 0.4 39608 5004 pts/0 S 23:33 0:00 /usr/bin/php -q /usr/local/bin/knx-tail2mysql
root 3168 0.0 0.0 4060 816 pts/0 S+ 23:33 0:00 grep knx-tail2mysql

Process en fond de tâche : OK

Si ce n’est pas le cas, et que aucune erreur n’est donnée dans le fichier de log, c’est peut-être que vous n’avez pas activé l’extension inotify.ini.
Pour débugger davantage, placer des System_Daemon::notice(« bla bla bla »);

J’allume ma lampe, en ligne de commande, via un interupteur, le résultat est le même.

# eibcommand -s 127.0.0.1 0/0/1 1 1
# cat /tmp/eibtrace
2013/04/18 22:58:57:598 - phys addr: 0.2.189 - IND low W group addr: 0/0/1 - value: 1 | 1 | 1 | 1 (81 - eis types: 1, 2, 7, 8)
2013/04/18 22:58:57:651 - phys addr: 1.1.2 - IND low W group addr: 0/0/1 - value: 1 | 1 | 1 | 1 (81 - eis types: 1, 2, 7, 8)
2013/04/18 22:58:57:708 - phys addr: 1.1.2 - IND low W group addr: 0/0/2 - value: 65 | 168 (a8 - eis types: 6, 14)

Trace dans le fichier : OK

Normalement, la table s’est mise à jour.

# mysql -pdbpass domy -e "SELECT ga_groupaddress,ga_value FROM knx_ga;"
+-------------------+-------------+
| ga_groupaddress   | ga_value   |
+-------------------+-------------+
| 0/0/1             | 1          |
| 0/0/2             | 65         |
+-------------------+-------------+

Trace dans le fichier : OK

Si la table MySQL ne se met pas à jour, mais que le fichier /tmp/eibtrace est à jour, consulter le fichier de log /var/log/knx-tail2mysql.log :

warning: [PHP Warning] filesize(): stat failed for /tmp/eibtrace [l:86]
warning: [PHP Warning] inotify_add_watch(): No such file or directory [l:90]

Alors que le fichier existe et que knx-tail2mysql tourne.
C’est probablement que l’ordre initialisation des scripts est incorrect.

OK, OK, OK ! L’extinction de la lampe, même effet, parfait, on continue !

Schema avec knx-tail2mysql

Schema avec knx-tail2mysql

Visualisation et contrôle par interface web

A ce stade, on a une table MySQL qui se met à jour selon l’état des actionneurs.
Qu’est-ce qui permet d’afficher en quasi-instantanné le contenu des champs MySQL ?
L’ajax voyons ! Tant mieux, je suis nul en AJAX !

Dans le cadre de gestion du lumière, il y’a 3 actions principales, donc 3 scripts vitaux :
– Récupérer l’état on/off ou la valeur d’éclairage.
– Envoyer un toggle on/off.
– Envoyer une valeur d’éclairage.

Je n’ai pas de volet roulant ou de gestion de chauffage, évidemment, plus il y’a de types d’élements, plus ça se complique ; j’ai voulu séparer dans une autre table les EIS pour cette raison , mais pour l’instant on va s’appliquer à allumer, éteindre et changer la valeur d’éclairage de mon ampoule sur le Hager TXA213.

J’ai choisi d’utiliser jQuery pour communiquer en AJAX, et ainsi profiter de la librairie JQuery-UI pour ce qui est graphique, slider, etc…
jQuery me permettra de travailler avec des noms pour appeler mes lumière, lamp1_onoff, lampe1_eclairage, lampe2cuisine_onoff, etc.. d’où le champs ga_name dans la table knx_ga.
Puis jQuery me permettra également d’utiliser JQuery Mobile pour les mêmes fonctions mais sur mobile.
J’utilise une classe PHP pour débugger, c’est écrit par Joe Fallon et ça s’appel PopupPhpDebug.

Deux div ID, l’un pour une image on/off, l’autre pour un slider avec la valeur d’éclairage.

Les fichiers sont téléchargables ici :
– Pensez à supprimer votre index.html dans /var/www/
– Conservez les droits www-data:www-data sur les fichiers et répertoires.

Telecharger

Je ne vais pas faire de cours de PHP, juste expliquer les grandes lignes du fonctionnement de mon interface web.

De l’AJAX pour piloter une lumière

Un simple div pour une simple lumière :

<img id="toggle_light-lampe1_onoff" alt="chargement" src="images/loading.gif" />

Dans l’attribut id :
toggle_ : sert de préfixe, tout ce qui est toggle_ correspond à une lumière.
lampe1_onoff : nom de la correspondance via ma table MySQL -> 0/0/1 dans ce cas.
Ainsi, autant toggle_* qu’il a de lumière.

Dans le même principe :

<div id="slider-lampe1_eclairage"></div>
<div id="text-lampe1_eclairage" align="center"></div>

slider-* pour un slider à les valeurs variables.
text-* pour afficher la valeur.

Interface jQuery pour KNX

Interface jQuery pour KNX

Et pour contrôler tout ça :


$(function() {
var intervalrefresh = 1000;

// fonction qui recupere la valeur d'un objet selon le nom du GA
$.get_light_value = function(light_name) {
 var result = null
 $.ajax({
 url: 'ajax/knx-get_light_value.php',
 data: 'ga_name='+light_name,
 dataType: 'json',
 async: false,
 success: function(data) {
 result = data['value'];
 //console.log("Valeur de : "+light_name+" -> "+result );
 }
 });
 return result;
}

// fonction qui toggle la valeur d'un object son le nom du GA (on/off)
$.set_light_toggle = function(light_name) {
 $.ajax({
 url: 'ajax/knx-set_light_toggle.php',
 data: 'ga_name='+light_name,
 dataType: 'json',
 async: false,
 success: function(data) {
 //console.log("Mise à jour de : "+light_name);
 }
 });
}

// fonction qui envois une valeur à un GA
$.set_light_value = function(light_name,value) {
 $.ajax({
 url: 'ajax/knx-set_light_value.php',
 data: 'ga_name='+light_name+'&ga_value='+value,
 dataType: 'json',
 async: false,
 success: function(data) {
 //console.log("Mise à jour de : "+light_name);
 }
 });
}

$(document).ready(function(){
 var refreshId = setInterval( function(){

// Refresh des lights
 $('[id^="toggle_light-"]').each(function() {
 var light_name_split = $(this).attr('id').split('-');
 var light_name = light_name_split[1];
 var light_value = $.get_light_value(light_name);
 if (light_value == 1) $('#toggle_light-'+light_name).attr('src',"images/lampes-on.png");
 if (light_value == 0) $('#toggle_light-'+light_name).attr('src',"images/lampes-off.png");
 });

// Refresh des sliders
 $('[id^="slider-"]').each(function() {
 var light_name_split = $(this).attr('id').split('-');
 var light_name = light_name_split[1];
 var light_value = $.get_light_value(light_name);
 $(this).slider( "option", "value", light_value);
 $('#text-'+light_name).html(light_value+"%");

});

}, intervalrefresh);
});

// mise à jour du bus KNX après mouvement du slider
 $('[id^="slider-"]').each(function() {
 var light_name_split = $(this).attr('id').split('-');
 var light_name = light_name_split[1];
 $(this).slider({
 max: 100,
 min: 0,
 stop: function(event,ui) {
 //console.log("nouvelle valeur du slider : "+ui.value);
 $.set_light_value(light_name,ui.value);
 }
 });
 });

// mise à jour des lumières après un click
 $('[id^="toggle_light-"]').click(function() {
 var light_name_split = $(this).attr('id').split('-');
 var light_name = light_name_split[1];
 $.set_light_toggle(light_name);
 });

// fin
});

Oula !
En gros, ça dit que toutes les 1000 ms secondes (1 seconde), pour chaque div qui commence par toggle_ d’executer le fichier ajax/knx-get_light_value.php , qui lui contient (via des includes et functions) une requête MySQL pour chercher la valeur correspondante en base.
Si c’est égal à 1, ça affiche une ampoule jaune, si c’est égal à 0, ça affiche une ampoule grise.
Idem pour le slider, on place le curseur à la valeur correspondante avec affichage sous forme texte.

Tandis qu’un click sur une ampoule, donc un div, a pour action d’exécuter le fichier ajax/knx-set_light_toggle.php , qui lui contient une récupèration de la valeur en base, il l’inverse, et l’applique sur le bus KNX.
Il l’applique sur le bus KNX via la librairie eibnetmux.php, souvenez vous, nous avions compilé eibnetmux avec les librairies PHP, c’est justement là qu’elle nous sert !

Le retour d’état de l’actionneur se fait par le cheminement eibtrace, knx-tail2mysql, ajax ; pas de « mise à jour directe », on évite ainsi les erreurs possibles, mais du coup, l’image s’actualise au pire qu’une seconde plus tard (+ temps de traitement de l’info), d’où le « quasi-instantanné ».

Schéma interaction KNX avec PHP

Schéma interaction KNX avec PHP

Ca marche bien !

Firebug pour surveiller l'AJAX

Firebug pour surveiller l’AJAX

La console firebug permet de visualiser les transactions AJAX -> MySQL, rajoutez des debugs si nécessaire.
Il est possible de diminuer les 1000ms pour obtenir plus de réactivité, c’est au serveur MySQL de pouvoir encaisser.

On peut également faire de l’image mapping sur une photo, grace à des outils tel que http://www.image-maps.com on définit un rectangle sur une lampe, modifie le code HTML pour placer notre « id ».

Créez votre map

Créez votre map

L’outil sert surtout pour créer la zonne au bon endroit.
Le code modifié :

<img id="Image-Maps_8201304191538462" alt="" src="http://www.image-maps.com/uploaded_files/8201304191538462_sketchup.png" usemap="#Image-Maps_8201304191538462" width="716" height="531" border="0" />
<map id="_Image-Maps_8201304191538462" name="Image-Maps_8201304191538462">
<area id="toggle_light-lampe1_onoff" title="Allumer/eteindre" alt="Allumer/eteindre" coords="510,143,579,266" shape="rect" />
</map>

Et un clique sur l’abat-jour de l’image éteind/allume la lumière.

Click pour allumer/eteindre

Click pour allumer/eteindre

Il doit certaiement être possible de modifier l’image de cette petite lampe pour qu’elle paraisse allumée.
Un autre exemple plus généraliste des maps sur image en CSS : cssplay.co.uk .

Les limites de la gestion de la maison deviennent celles du graphisme web, du moment où on a accès à l’état des éléments KNX, qu’on peut les piloter, techniquement c’est presque sans limite ; il faut savoir s’y faire ergonomiquement, c’est essentiel.

Interface mobile pour téléphone et tablette

Pour l’instant, c’est très brouillon, pas d’AJAX, je pars du principe que si je contrôle ma maison avec mon smartphone, c’est parce que je ne suis pas chez moi, donc pas de rafraichissement instantanné.
J’utilise jQuery Mobile, une demo est disponible sur le site officiel.
Mon utilisation est celle d’un formulaire traité en PHP classique, pas très au point pour l’instant, mais fonctionnel pour un début.

Il me semble qu’il est possible de convertir un site web en application iphone/android, il faut que je retrouve le nom de cet outil.
A terme, le tout protégé par htacces ou authentification php.

Inteface mobile

Inteface mobile

Rendu et réactivité

Une vidéo afin de se rendre compte de la réactivité du click et de la remonté d ‘infos, malgré l’utilisation d’une webcam de piètre qualité, avec une mise à niveau de la luminosité catastrophique, je n’ai pas trouvé mieux pour filmer la réaction du click sur la lampe.

Conclusion

Mon but étant d’arriver à contrôler l’installation domotique depuis un serveur par une interface web, c’est en bonne voie  !

Quelques points à approfondir lorsque j’aurai une installation KNX réele et complète :
– Groupe d’adresse différente entre l’état et l’action ? Vraiment ? Reste à voir comment gérer ça.
– Gestion des autres EIS : volets, BSO, chauffage, etc…
– Ajout d’intelligence avec du 1-wire.
– Une interface web ergonomiquement parfaite.

C’est relativement réactif, j’en attends pas plus pour l’instant.

Je n’utilise aucun cache EIBD, voir les commentaires sur l’article Se passer de linknx et webknx2 une fausse bonne idee ?

C’est donc une bonne base pour une plateforme de contrôle et supervision du bus KNX, j’ai hâte d’approfondir sur une installation réel .

Téléchargement

Je met à disposition une image de la carte SD de mon Raspberry Pi.
Attention, c’est très lourd : 7,42 Go. Sauvegardé avec Win32DiskImager, vous devriez pouvoir restaurer avec le même outil.

Mot de passe root : pi
Mot de passe MySQL : dbpass

Telechargerraspberry-domolio-knx.img : 7,42 Go
MD5 : 81a8df6724fea6c8916f6a8e814b908f

Have fun !

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

52 réponses à Une interface web pour contrôler le bus KNX sur Raspberry Pi

Laisser un commentaire

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