Category Archives: Web

Suvi de la consommation d’eau avec des compteurs à impulsions sur un bus 1-wire

Contexte

Si vous avez suivi les posts précédents, vous savez peut-être que j’ai aménagé dans une maison neuve, et la construction a ses avantages, comme le fait de penser à installer des compteurs à impulsion par le sanitaire, puis une prise réseau à proximité par l’electricien, ainsi je me retrouve avec ça :

Compteur à impulsion en attente

Compteur à impulsion en attente

Le but étant de suivre la consomation d’eau chaude et d’eau froide.
Le compteur pour l’eau froide est placé avant la nourrice et après l’alimentation du CESI.
Le compteur pour l’eau chaude est placé à la sortie du CESI.
CQFD : inutile dans mon cas de déduire l’eau froide sur l’eau chaude.

Principe

Je vais utiliser un Raspberry Pi et utilise le bus 1-wire pour le comptage des impulsions.
En 1-wire, j’utilise l’interface DS1490 (USB/1-wire) que j’ai acqueri lors des mes découvertes du 1-wire ; car sauf erreur ma part, l’utilisation du 1-wire par les pins du Raspberry Pi ne permettent pas d’exploitrer un DS2423.
Un DS2423 est un double compteur avec mémoire.
Plus tard, je profiterai d’avoir un bus 1-wire pour mettre des sondes de températures DS18B20 à coller aux tuyaux du chauffage qui passent à coté.

Le raspberry est alimenté par le réseau, de plus, ayant besoin d’une alimentation 5 volts pour les composants 1-wire, je ne la prends pas du USB, mais du PoE passif grâce à un simple splitter.

Un schéma de principe :

compteur-impulsion-raspberry-principe

Schéma de principe

 

Dans l’ordre :

  • Les compteurs d’eau génère une impulsion à chaque littre qui passe.
  • Le DS2423 compte chaque impulsion de chaque compteur.
  • Le raspberry consulte le compteur du DS2423.
  • Le raspberry envoi le chiffre obtenu dans une table MySQL sur un serveur.
  • Une page web traire ces chiffres de la table pour générer des graphiques.
  • Je peux me faire la morale parce que mes douches sont trop longues.

Go ?!

Montage du DS2423 pour compter des impulsions

Pour commencer, le DS2423 n’est plus produit, il est en fin de vie.
On peut encore s’en procurer chez hobby-boards.com à 10.50 $ / pièce, auquel on rajoute 10,55 $ de frais de port ! Ce n’est pas rien !

Voici comment j’utilise le DS2423 :

ds2423 schéma

ds2423 : schéma

Il faut donc fournir un bus 1-wire composé d’un « DATA » et d’une masse. Il faut également fournir une alimentation 5V qui a une masse commune avec le bus 1-wire.
Pour l’alimentation, je vais m’inspirer de mon injecteur 5 Volts → 1Wire, sauf que je vais l’inclure dans un seul et même boitier.

 

Schéma injecteur 1-wire

Schéma injecteur 1-wire

+

ds2423 schéma

ds2423 schéma

=

ds2423 avec alimentation

ds2423 avec alimentation

Je me laisse la possibilité de repiquer le bus 1-wire et une alimentation (à droite du schéma) pour ajouter un autre DS2423 dans le but de compter un 3ième compteur à impulsion, voir un 4ième ou un pluviomètre qui se comporte de la même façon qu’un compteur d’eau à impulsion.

Réalisation et mise en place

Est-ce que la théorie rejoint la pratique ?
Je suis parti sur une « prototype board », des borniers, le tout placé dans une prise RJ45 en saillie.
L’avantage d’une prise RJ45 en saillie, c’est quelle permet de planquer le PCB, le tout se  fixe proprement au mur. De plus, j’y connecte à travers le RJ45, un câble RJ11 du DS9490R.
Place aux photos.

Comme on peut le voir, il y a deux DS2423 sur ce montage, c’est en prévision d’un 3ième compteur à impulsion pour un pluviomètre.
Pour ne pas compliquer la chose, on va faire comme si ce deuxième DS2423 n’existe pas.
Sur la dernière photo, on voit clairement les deux compteurs à impulsion raccordés au montage.
La LED permet de vérifier que le circuit est bien alimenté.
Le 5 Volts, provient de l’USB du Raspberry Pi, mais il est préférable de le sortir d’une alimentation sans rapport aux Raspberry Pi, ce qui a été fait entre temps, j’ai utilisé un splitter DC 3,5mm pour alimenter le montage ; le 5V provient du PoE passif.

Récupération des valeurs des compteurs

C’est partie pour une installation de Raspbian sur le Raspberry Pi.
Mes quelques commandes de bases sur le Raspberry :

# sudo passwd
# su
# apt-get update
# apt-get dist-upgrade
# apt-get install htop vim screen usbutils

Puis les choses sérieuses commecent, on branche le dongle DS1490 :

# lsusb
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp.
Bus 001 Device 004: ID 04fa:2490 Dallas Semiconductor DS1490F 2-in-1 Fob, 1-Wire adapter

On installe owserver, owfs-fuse et surtout owhttps qui permettra de visualiser facilement l’état des compteurs le temps de scripter tout ça.

# apt-get install owserver owfs owhttpd

Puis on modifie le fichier /etc/owfs.conf :
On commente la ligne 16 et décommente la ligne 19.

# vi /etc/owfs.conf
# ...and owserver uses the real hardware, by default fake devices
# This part must be changed on real installation
#server: FAKE = DS18S20,DS2405
#
# USB device: DS9490
server: usb = all
#
# Serial port: DS9097
#server: device = /dev/ttyS1
#
# owserver tcp address
#server: server = 192.168.10.1:3131

Puis on redemarre owserver

# /etc/init.d/owserver restart
[ ok ] Restarting 1-Wire TCP Server: owserver.

Direction le navigateur pour consulter owhttp : http://adresseipduraspberry:2121
Owhttp devrait lister une sonde DS2423 commençant par 1D. 

DS2423 sur OWHTTPD

DS2423 sur OWHTTPD

Ohhhh ! counters.A correspond à mon compteur d’eau froide ; counters.B à mon compteur d’eau chaude.
Après un petit test au verre mesureur, 1 litre d’eau = 1 impulsion. Autrement, la valeur d’une impulsion est généralement marquée sur le compteur, ce n’est pas le cas pour ma part.

Bon, c’est en bonne voie, il reste maintenant à scripter tout ça afin d’alimenter une base de données MySQL sur un serveur distant.
A noter qu’on peut également ecrire les relevé des compteurs sur une base MySQL en local sur le Raspberry ; dans mon cas j’utilise un serveur distant pour d’autres applications lié à la domotique et donc je centralise toutes les données sur une même machine.

Pour insérer les valeurs des compteurs, je m’inspire d’un des mes tuto : Insérer les valeurs 1-wire dans une base MySQL.

Coté serveur MySQL (local/distant, à adapter selon votre convenance)

Nécéssite apache2, php5 et mysql-server  : apt-get install apache2 mysql-server php5
Je crée une base et les deux tables, comme dans mon article, à l’exception de la table 1wire_sensor qui est modifiée pour prendre en compteur les compteurs.
Ce qui donne :

# mysql -p
create database 1wire;
use 1wire;
CREATE TABLE IF NOT EXISTS `1wire_sensor` (
`1wire_sensor_id` int(3) NOT NULL AUTO_INCREMENT,
`1wire_sensor_enable` enum('true','false') NOT NULL,
`1wire_sensor_type` enum('temperature','presence','sensed.A','counters.A','counters.B') NOT NULL,
`1wire_sensor_family` tinytext NOT NULL,
`1wire_sensor_idaddress` tinytext NOT NULL,
`1wire_sensor_comment` text NOT NULL,
PRIMARY KEY (`1wire_sensor_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
CREATE TABLE IF NOT EXISTS `1wire_data` (
`1wire_data_id` int(11) NOT NULL AUTO_INCREMENT,
`1wire_data_sensor_id` int(3) NOT NULL,
`1wire_data_timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`1wire_data_value` text NOT NULL,
PRIMARY KEY (`1wire_data_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
GRANT ALL PRIVILEGES ON 1wire.* TO '1wire'@'%' IDENTIFIED BY '1wire';

En détail, j’ai ajouté counters.A et counters.B dans le champs 1wire_sensor_type.
Ceci afin de m’adapter aux DS2423 ; counters.A et counters.B sont les noms exact des champs a récupérer.
Faut penser à permettre à notre serveur MySQL d’autoriser les connexions distantes et commenter la ligne bind-address.

# vi /etc/mysql/my.cnf
#
# Instead of skip-networking the default is now to listen only on
# localhost which is more compatible and is not less secure.
# bind-address = 127.0.0.1
#
# /etc/init.d/mysql restart

Et voila, la partie serveur se termine là pour l’instant, nous y reviendrons pour créer un graphique avec la consomation d’eau.

Pour résumer :
Host : 192.168.1.100
Database MySQL : 1wire
User MySQL : 1wire
Password MySQL : 1wire

Coté RaspberryPi

Là encore, je m’inspire de mon article de l’époque, cependant mon fichier PHP ne traite que les sondes de témpérature et de présence, quelques adaptations sont nécessaires pour explorer un DS2423 :

# cd /root/
# wget https://www.domolio.fr/wp-content/uploads/2012/05/1-wire-mysql.tgz
# tar zxvf 1-wire-mysql.tgz
# cd 1-wire/

J’apporte de suite les informations nécessaires pour la connexion au serveur :

# vi require/main.inc.php

A noter que je travail dans /root/1-wire .

<?php

// -- BASE --

$DEBUG = true;
$DEBUG_TXCOLOR = "#fff";
$DEBUG_BGCOLOR = "#000";
$PATH = "/root/1-wire";

// --

define("DEBUG", $DEBUG);
define("DEBUG_TXCOLOR", $DEBUG_TXCOLOR);
define("DEBUG_BGCOLOR", $DEBUG_BGCOLOR);
define("PATH", $PATH);

// ----
// -- MYSQL --

// Préfixe de toutes les tables du site
$MYSQLHOST = "192.168.1.100";
$MYSQLLOGIN = "1wire";
$MYSQLPWD = "1wire";
$MYSQLBASE = "1wire";

//--

Ah oui, il faut php et php5-mysql

# apt-get install php5-cli php5-mysql mysql-client

Et je créer mon script qui récupère les valeurs des sondes :

# vi /usr/local/bin/1wire-fetch.php

Puis je copie/colle le script de mon article précédent, modifié pour permettre l’intérogation de sondes DS2423 :

#!/usr/bin/php
<?php
$pathconfig = "/root/1-wire";
require_once($pathconfig."/require/main.inc.php");
require_once(PATH."/require/database.inc.php");
require_once(PATH."/require/function-1wire.inc.php");

// Recup des données des sondes depuis la BDD
$req_sensor = "SELECT * FROM 1wire_sensor WHERE 1wire_sensor_enable IS TRUE";
$qur_sensor = mysql_query($req_sensor);
while($dat_sensor = mysql_fetch_array($qur_sensor)) {
 // Pour chaque sonde on constitue son adresse :
 $onewire_address = $dat_sensor['1wire_sensor_family'].".".$dat_sensor['1wire_sensor_idaddress'];
 // On teste sa présence sur le bus
 if(onewire_presence($onewire_address)) {
 debug("OUI ! ".$onewire_address." est présent sur le bus","cli");
 if ($dat_sensor['1wire_sensor_type'] == "presence") {
 debug("Seul sa presence sur le bus est nécessaire","cli");
 // on ecris dans la BDD qu'il est présent et on s'arrete là
 $value = 1;
 }
 elseif ($dat_sensor['1wire_sensor_type'] == "temperature" || $dat_sensor['1wire_sensor_type'] == "sensed.A" || $dat_sensor['1wire_sensor_type'] == "counters.A" || $dat_sensor['1wire_sensor_type'] == "counters.B" ) {
 // on récupère sa valeur, que ca soit la température ou l'état du PIO (DS2406) ou le compteur (DS2423).
 $value = onewire_read($onewire_address,$dat_sensor['1wire_sensor_type']);
 }
 }
 else {
 // Composant absent du bus
 debug ("NON ! ".$onewire_address." n'est pas présent sur le bus","cli");
 $value = 0;
 }

 // on écris le resultat dans la BDD
 $req_insdata = "INSERT INTO 1wire_data SET
 1wire_data_sensor_id = ".$dat_sensor['1wire_sensor_id'].",
 1wire_data_value = '".$value."'";
 mysql_query($req_insdata) or die ("Erreur à l'insertion des données 1-wire : ".mysql_error());
}
mysql_close();
?>
# chmod +x /usr/local/bin/1wire-fetch.php

Puis on fait un break ! Une petite explication s’impose !
Le script 1wire-fetch.php sera appelé par le cron pour remplir une base de donnée sur un serveur distant.
Ce script fait appel à des fichiers stockés dans /root/1-wire/require/ ; ces fichiers sont des « functions » que j’ai pondu il y a quelque temps en utilisant une libraire fournis par l’équipe qui développe 1wire sur linux (owfs.org).
La libraire maitresse, qui permet d’intéroger des sondes par PHP, s’appel libownet-php et fournis un fichier miraculeux qui se nomme ownet.php.

Pour aller plus loin, consulter mon article : Interroger les sondes 1-wire par un script PHP.

Le fichier ownet.php fourni par le paquet libownet-php est buggé, dans l’état, il ne permet pas d’exploiter les DS2423, de plus, c’est ce même paquet que j’ai intégré dans l’archive que vous avez décompressé tout à l’heure.
Mais c’est pas grave, on va débugger ça ensemble.

Pour commencer, adios le fichier ownet.php de mon archive, on va reprendre la version packagé par Debian.

# apt-get install libownet-php
# cd /root/1-wire/require/
# cp /usr/share/php/OWNet/ownet.php .

Puis deux corrections sont nécessaire :

# vi ownet.php

Ligne 516 :

if (bccmp($ret['data_php'],0,0)==-1){

Devient,

if (bccomp($ret['data_php'],0,0)==-1){

Ligne 484 :

$ret['data']    =substr($data,0,$data_len);     // data_php length is the same as $ret[2]

Devient,

$ret['data']    =trim(substr($data,0,$data_len));     // data_php length is the same as $ret[2]

Le premier bug bccmp/bccomp est assez connu, traine depuis très longtemps.
Le second bug est propre au DS2423, j’ai trouvé un seul appel à l’aide sur internet, datant de mars 2010 :
Cannot acces counters DS2423 with ownet.php
C’est donc bien lié aux espaces qui trainent devant la valeur du compteur, chose corrigé avec la fonction php trim().

Ou plus simplement, télécharger directement ma version du fichier ownet.php corrigée :

# cd /root/1-wire/require/
# wget "https://domolio.fr/wp-content/uploads/2015/01/ownet.php.txt" -O ownet.php

Voila, notre break/débug est terminé, pour résumer step by step :

  1. Le cron lance le script /usr/local/bin/1wire-fetch.php
  2. Ce script utilise les fichiers stocké dans /root/1-wire pour se connecter au serveur MySQL distant et au bus 1-wire
  3. Le script consulte la table MySQL 1wire_sensor pour lister les sondes à intéroger sur le bus
  4. Ce même script consulte chaque sonde en verifiant sa présence sur le bus 1-wire et récupère la valeur souhaitée.
  5. Toujours ce script, ajoute les valeurs récupérées dans la table MySQL 1wire_data du serveur distant.
  6. Le script est terminé, il recommencera dans une heure.
  7. Une page web sur notre serveur distant, intéroge les valeurs dans les différentes tables MySQL pour afficher de beaux graphiques.

Ainsi nous allons maintenant alimenter la table 1wire_sensor avec les sondes à intéroger.
Et comme je suis sûr de moi, je le fais depuis mon client, selon les informations MySQL que je me suis mis de coté :

# mysql -h192.168.1.100 -u1wire -p1wire 1wire
INSERT INTO 1wire_sensor SET 1wire_sensor_enable='true', 1wire_sensor_type='counters.A', 1wire_sensor_family='1D', 1wire_sensor_idaddress='641710000000', 1wire_sensor_comment='eau froide';
INSERT INTO 1wire_sensor SET 1wire_sensor_enable='true', 1wire_sensor_type='counters.B', 1wire_sensor_family='1D', 1wire_sensor_idaddress='641710000000', 1wire_sensor_comment='eau chaude';

Pour rappel :
1wire_sensor_family=’1D’ : 1D correspond à la famille des DS2423.
1wire_sensor_idaddress=’641710000000′ : 641710000000 correspond à l’ID de ce DS2423 tel que owhttp me l’a indiqué tout à l’heure.

Il ne reste plus qu’à tester :

# php /usr/local/bin/1wire-fetch.php
[function] onewire_presence pour : 1D.641710000000
OUI ! 1D.641710000000 est présent sur le bus
[function] onewire_read pour : 1D.641710000000 et le type counters.A
La variable value est vide pour le mode counters.A, on recommence (1 fois est normal)
Valeur brute trouvée :972
[result] Donnée trouvée pour /1D.641710000000/counters.A : 972
[function] onewire_presence pour : 1D.641710000000
OUI ! 1D.641710000000 est présent sur le bus
[function] onewire_read pour : 1D.641710000000 et le type counters.B
La variable value est vide pour le mode counters.B, on recommence (1 fois est normal)
Valeur brute trouvée :638
[result] Donnée trouvée pour /1D.641710000000/counters.B : 638

Oh 🙂
Grâce à la première ligne du script ( #!/usr/bin/php ) on peut même directement lancer 1wire-fetch.php :

# 1wire-fetch.php
[function] onewire_presence pour : 1D.641710000000
OUI ! 1D.641710000000 est présent sur le bus
[function] onewire_read pour : 1D.641710000000 et le type counters.A
La variable value est vide pour le mode counters.A, on recommence (1 fois est normal)
Valeur brute trouvée :975
[result] Donnée trouvée pour /1D.641710000000/counters.A : 975
[function] onewire_presence pour : 1D.641710000000
OUI ! 1D.641710000000 est présent sur le bus
[function] onewire_read pour : 1D.641710000000 et le type counters.B
La variable value est vide pour le mode counters.B, on recommence (1 fois est normal)
Valeur brute trouvée :638
[result] Donnée trouvée pour /1D.641710000000/counters.B : 638

Au passage, une petite chasse d’eau + lavage de main aura utilisé 3 litres.
Coté mysql, je devrais avoir toutes les données :

select * from 1wire_data;
+---------------+----------------------+----------------------+------------------+
| 1wire_data_id | 1wire_data_sensor_id | 1wire_data_timestamp | 1wire_data_value |
+---------------+----------------------+----------------------+------------------+
|             1 |                    1 | 2015-01-13 00:37:06  | 975              |
|             2 |                    2 | 2015-01-13 00:37:06  | 638              |
+---------------+----------------------+----------------------+------------------+
2 rows in set (0.00 sec)

C’est le cas !

Je mets en place le cron pour une execution toutes les heures :

# vi /etc/cron.d/crononewire
# Récup des valuers 1-wire tout les 1h et 5 minutes
5 * * * * root /usr/local/bin/1wire-fetch.php

Donc le script se lancera à 13h05, 14h05, 15h05, etc…

Visualisation des données

Le temps passe, la table MySQL se remplit, place aux graphiques !
J’utilise Highcharts et plus précisement sur Highstock qui a l’avantage d’avoir une échelle du temps.
On part du principe que sur la machine qui herberge la partie web pour les graphiques dispose des outils nécessaire : apache2, php-mysql, php5, etc..
Bien entendu, cette partie peut également se situer sur le raspberry, tout comme les données MySQL.

# cd /var/www
# wget https://www.domolio.fr/wp-content/uploads/2015/10/graph-eau.tar.gz
# tar zxvf graph-eau.tar.gz
# cd eau

Le graphique se scinde en deux parties :
– data.php qui récupère les valeurs dans la base de donnéess et qui met en forme au format json.
– index.php et son répertoire « js » qui traite les données et crée les graphiques.

# vi data.php
<php 
header('Cache-Control: no-cache, must-revalidate');
header('Content-type: text/javascript');

$mysql_host = "127.0.0.1";
$mysql_db = "1wire";
$mysql_user = "1wire";
$mysql_pwd = "1wire";

$id_eau_froide = 1;
$id_eau_chaude = 2;

Mon eau froide correspond à l’id « 1wire_data_sensor_id » n°1 dans la table 1wire_data qui correspond à COUNTERS.A de mon DS2423.
L’eau chaude à l’id n°2 -> COUNTERS.B.

Dans le fichier index.php, une seule modification est importante :

$pointStart = "Date.UTC(2015, 0, 13, 0, 0, 0)"
...
$pointStart =  "Date.UTC(2015, 0, 13)";
...
$pointStart =  "Date.UTC(2015, 0)";

« 2015, 0, 13, 0, 0, 0 » correspond à mon tout premier relevé de compteurs :
Le 13 janvier 2015 à 01h soit 00h GMT.

Ici, HighStock travail en intervalle avec les données json reçues par le fichier data.php
On récupère la valeur des compteurs toutes les heures, donc l’intervalle entre deux points pointInterval pour le format :
– Journalier : 3600000 millisecondes (3600 x 1000).
– Hebdomadaire : 3600 x 1000 x 24 x 7.
– Mensuel : 3600 x 1000 x 24 x 31.

Là est le petit faux dans ce script, tous les mois ne font pas 31 jours…
Ça implique un petit décalage visuel de mois en mois.
A corriger à l’occasion…

Voila le résultat :

consomation eau hebdomadaire

consomation eau hebdomadaire

consomation d'eau heure par heure

consomation d’eau heure par heure

consomation d'eau par mois

consomation d’eau par mois

Demo

Une demo figée au 11/10/2015 ici :

http://www.chocolio.com/demo-eau/

Et voila ! Très content de mes compteurs et de mon suivi !
La prochaine étape pour moi, sera d’ajouter un pluviomètre sur le dernier compteur du DS2423, et cela afin d’actionner ou pas un arrosage automatique.

Posté dans 1-wire, Debian, graphs, php, Raspberry Pi, Web | 6 Commentaires

Monitorer la temperature avec un Raspberry Pi, vite fait bien fait

L’hiver arrive, dans la nouvelle maison je souhaite surveiller la température temporairement pièce par pièce pour mesurer l’impact du chauffage.
Pour cela, je m’aide d’un Raspberry Pi en WIFI et d’une sonde 1-wire DS18B20.

Je ne vais pas réinventer le fil à couper le beurre, le schéma de câblage d’un DS18B20 est le même sur tous les blogs :

DS18B20 sur raspberry pi

DS18B20 sur raspberry pi

 

Ce qui donne une fois soudé :

Magnifique tapis de souris

Magnifique tapis de souris

Vite fait bien fait on a dit

Vite fait bien fait on a dit

Avec des pins !

Avec des pins ! Luxe !

J’utilise une résistance de 4.2kΩ qui fait très bien l’affaire.

Coté Raspberry Pi, après la configuration du WIFI, place à la configuration du 1-wire.

# vi /etc/modules
w1-therm
w1-gpio pullup=1
snd-bcm2835

Pour une action immédiate :

# modprobe w1-therm
# modprobe w1-gpio pullup=1

Puis allons voir ce qu’il se passe  du coté du bus 1-wire :

# cd /sys/bus/w1/devices
# ls -l
total 0
lrwxrwxrwx 1 root root 0 nov. 18 00:05 28-000003977b0a -&gt; ../../../devices/w1_bus_master1/28-000003977b0a
lrwxrwxrwx 1 root root 0 nov. 17 22:45 w1_bus_master1 -&gt; ../../../devices/w1_bus_master1

Ah ! 28 machins, ça me rappelle des souvenirs.

# cd 28-000003977b0a
# cat w1_slave
63 01 4b 46 7f ff 0d 10 15 : crc=15 YES
63 01 4b 46 7f ff 0d 10 15 t=22187

Bon, on devine que 22187 correspond à 22,187°C.
Autrement on peut utiliser cette commande :

# find /sys/bus/w1/devices/ -name "28-*" -exec cat {}/w1_slave \; | grep "t=" | awk -F "t=" '{print $2/1000}'
22.187

Voila voila…

Reste à grapher périodiquement cette valeur.
Vite fait, bien fait, j’aime grapher avec munin et j’ai vite trouvé un script simple et efficace :
https://gist.github.com/kminiatures/4f3…

Ah bah !?! Go !

# apt-get install -y munin-node bc
# cd /etc/munin/plugins/

Personnellement, je souhaite juste ma temperature, donc je supprime tous les plugins et crée le nouveau plugin :

# rm *
# vi 1w-temp

Pour y insérer ceci (selon https://gist.github.com/kminiatures/4f3… )

#!/bin/sh

case $1 in
   config)
        cat <<'EOM'
graph_title Temperature
graph_vlabel Temperature
temperature.label temperature
EOM
        exit 0;;
esac

printf "temperature.value "
TEMP=`cat /sys/bus/w1/devices/*/w1_slave | grep t= | sed s/.*t=//`
echo "scale=2; $TEMP / 1000" | bc

Puis on teste tout de suite notre plugin :

# chmod +x 1w-temp
# munin-run 1w-temp
temperature.value 22.25

Bientôt la fin… J’ajoute un « allow * » au fichier /etc/munin/munin-node.conf
Puis /etc/init.d/munin-node restart

Mettons un serveur munin/apache2 en place :

# apt-get install munin apache2
# vi /etc/apache2/conf.d/munin

Puis remplaçer toutes les occurences de Allow from localhost 127.0.0.0/8 ::1 par Allow from all .
On redémarre apache2 :

# /etc/init.d/apache2 restart

Vous pouvez également modifier le fichier /etc/munin/munin.conf pour modifier le nom de votre serveur.

Evidemment, si vous avez un autre serveur munin (mon cas), inutile d’installer un serveur munin sur votre Raspberry, il vaut mieux décharger cette tâche à une autre machine plus apte à ce genre de service.
Au bout de quelques heures ou quelques jours, vous devriez voir ce genre de graphique :

DS18b20 dans munin

DS18b20 dans munin

DS18b20 zoomé dans munin

DS18b20 zoomé dans munin

Bon, vite fait, maintenant qu’on a un apache2 sur le raspberry, une petite page PHP pour connaitre la température actuelle de la sonde.
On peut aussi lire la valeur « Cur: » sur les graphs munin, mais mes petits yeux veulent quelque chose de plus grand, surtout depuis le téléphone.

# apt-get install php5
# cd /var/www/
# vi index.php
<meta name="viewport" content="width=device-width, user-scalable=no">
<center><span style="font-size:65px;">
<?php
// Fichier à lire
$file = "/sys/bus/w1/devices/28-000003977b0a/w1_slave";

// Lecture ligne par ligne
$lines = file($file);

// Recupere la 2nd ligne
$temp = explode('=', $lines[1]);

// Formatage de la temperature
$temp = number_format($temp[1]/1000,2, '.', '');

// On affiche la temperature
echo $temp;
?>
</center></span>

28-000003977b0a correspond à l’ID de ma sonde DS18B20.

Ce qui donne sur smartphone :

page web DS18b20

page web DS18b20

Voila, un article vite fait bien fait, pour surveiller une température.
Cela va me permettre dans un premier temps de calibrer mon Z41.
Puis ça me permettra d’analyser les variations de température de la maison entre le chauffage au sol, le poêle à pellet, et l’ensoleillement à travers les ouvertures.

Posté dans 1-wire, graphs, php, Raspberry Pi, Web | Tagged , , , , | 19 Commentaires

Téléinfo et emoncms, rapide retour d’expérience

Un petit billet pour dire que j’ai commencer à superviser le compteur électrique EDF (téléinfo) avec emoncms.

J’ai suivi deux articles :

Deux petites difficultés:

  • Je n’avais pas désactivé la console série sur le Raspberry Pi, alors que c’est indiqué dans ici : Teleinfo – Préparation d’une Raspberry Pi.
  • Une fois mes données dans emoncms, je ne sais pas trop quoi en faire, sources, feeds, process, nodes, ça ne m’inspire pas trop pour l’instant, mais je me dépatouille.

Quelques photos de l’installation pour les essais :

Lorsque l’installation sera « validée », le téléinfo empruntera une paire en attente dans le boîtier DTI et le raspberry trouvera sa place dans la baie 19″, la partie photocoupleur sera dans un joli petit boitier.


Je ne connaissais pas du tout emoncms, je me suis laissé guider par les nombreux tuto qui existent.
Comme dit un peu plus haut, ma difficulté est de traiter les données reçues.

Emoncms sources

Sources sur Emoncms

 

Feeds emoncms

Feeds emoncms

 

Emoncms visu

La visu sur Emoncms

Si quelqu’un a des conseils à donner sur la façon de traiter les valeurs du téléinfo, je suis preneur…
Le script présenté par hallard.me a l’avantage de pouvoir alimenter emoncms et une table MySQL, ce qui va me permettre de tester d’autres solutions de graphs en parallèle d’emoncms.

Le script de hallard.me disponible sur github, permet en effet de remplir une table MySQL.
Pour ma part, ça représente une avantage important : ne maitrisant pas emoncms, une table MySQL me permet de maintenir une source brutte des données du téléinfo.

La table est constituée ainsi (CTRL+C / CTRL+V de hallard.de) :

CREATE TABLE IF NOT EXISTS `DbiTeleinfo` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `DATE` datetime DEFAULT NULL,
  `ADCO` varchar(12) DEFAULT NULL,
  `OPTARIF` varchar(4) DEFAULT NULL,
  `ISOUSC` decimal(2,0) DEFAULT NULL,
  `BASE` decimal(9,0) DEFAULT NULL,
  `HCHC` decimal(9,0) DEFAULT NULL,
  `HCHP` decimal(9,0) DEFAULT NULL,
  `BBRHCJB` decimal(9,0) DEFAULT NULL,
  `BBRHPJB` decimal(9,0) DEFAULT NULL,
  `BBRHCJW` decimal(9,0) DEFAULT NULL,
  `BBRHPJW` decimal(9,0) DEFAULT NULL,
  `BBRHCJR` decimal(9,0) DEFAULT NULL,
  `BBRHPJR` decimal(9,0) DEFAULT NULL,
  `DEMAIN` varchar(4) DEFAULT NULL,
  `EJPHN` decimal(9,0) DEFAULT NULL,
  `EJPHPM` decimal(9,0) DEFAULT NULL,
  `PEJP` varchar(2) DEFAULT NULL,
  `PTEC` varchar(4) DEFAULT NULL,
  `IINST1` decimal(3,0) DEFAULT NULL,
  `IINST2` decimal(3,0) DEFAULT NULL,
  `IINST3` decimal(3,0) DEFAULT NULL,
  `IMAX1` decimal(3,0) DEFAULT NULL,
  `IMAX2` decimal(3,0) DEFAULT NULL,
  `IMAX3` decimal(3,0) DEFAULT NULL,
  `HHPHC` varchar(1) DEFAULT NULL,
  `PMAX` decimal(5,0) DEFAULT NULL,
  `PAPP` decimal(5,0) DEFAULT NULL,
  `ADPS` decimal(3,0) DEFAULT NULL,
  `MOTDETAT` varchar(6) DEFAULT NULL,
  `TENSION` decimal(3,0) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `SEARCH_INDEX` (`ADCO`,`DATE`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Idéale pour le script suivant :

https://github.com/BmdOnline/Teleinfo

Voici les données à modifier du fichier structure.date.php afin de correspond aux données soumises par le script de hallard.me :

    // Adapter les valeurs du tableau si le nom du champ est différent
    "table" => array (
        "DATE"     => "DATE",      // => généralement, vaut soit "DATE", soit "TIMESTAMP"
        "OPTARIF"  => "OPTARIF",   // option tarifaire souscrite
        "ISOUSC"   => "ISOUSC",    // intensité souscrite
        "BASE"     => "BASE",      // BASE
        "HP"       => "HCHP",      // HCHP
        "HC"       => "HCHC",      // HCHC
        "HPJB"     => "BBRHPJB",   // BBRHPJB
        "HPJW"     => "BBRHPJW",   // BBRHPJW
        "HPJR"     => "BBRHPJR",   // BBRHPJR
        "HCJB"     => "BBRHCJB",   // BBRHCJB
        "HCJW"     => "BBRHCJW",   // BBRHCJW
        "HCJR"     => "BBRHCJR",   // BBRHCJR
        "HN"       => "EJPHN",     // EJPN
        "HPM"      => "EJPHPM",    // EJPHPM
        "PTEC"     => "PTEC",      // période tarifaire en cours
        "DEMAIN"   => "DEMAIN",    // prévision du lendemain (formule Tempo)
        "IINST1"   => "IINST1",    // => généralement, vaut soit "IINST1" soit "INST1"
        "PAPP"     => "PAPP"       // puissance apparente
    )

Et voilà, j’ai certe deux outils pour presque la même chose :
EmonCMS, très complet et difficile à maitriser à mon gout, il me faudra du temps pour avoir un monitoring au top.
Teleinfo, simple, très facile à mettre en place, fait le boulot.

 

Teleinfo instantanne

Teleinfo instantanné

Teleinfo sur 24h

Teleinfo sur 24h

Teleinfo historique

Teleinfo historique


Coté tableau électrique, j’ai validé ma période de test et ai sorti le SFH620 de sa platine de tests :

Petite astuce, j’ai collé l’ensemble photocoupleur, bornier, résistance, au boitier du Raspberry en tailladant le plastique pour la colle prenne.

 

Posté dans emoncms, graphs, teleinfo, Web | 6 Commentaires

Virtualiser XPEnology avec Xen pour profiter de Surveillance Station

Dans le choix d’un serveur de vidéo-surveillance (NVR), j’ai longtemps cherché une solution opensource, le premier qui vient, serait ZoneMinder, mais mes tests m’ont permis de le trouver trop lourd, barbant, instable, pas user-friendly.
J’ai continué ma quête du NVR parfait, même dans les freeware et proprio pour un usage habitat. Rien à l’horizon…
Puis je suis tombé sur Surveillance Station disponible sur les NAS Synology, les vidéos Youtube m’ont vendu du rêve (1) (2)

Acheter un NAS Synology pour la vidéo-surveillance, si ça tourne bien, ok…
Mais les NAS « physique » ont des limites de caméras… C’est marqué en petit dans la caractéristique technique de chaque produit de Synology.
Le DS414j par exemple, supporte au maximum 8 caméras.

Documentation technique DS414j

Morceau choisi de la documentation technique DS414j

Je suppose que du nombre de baie découle la puissance de matériel et donc du nombre de caméras possible.
Je compte installer entre 5 et 8 caméras, donc il faut que je compte ~ 300 € TTC sans disque pour acheter le NAS suffisament performant.

XPEnology

Puis j’ai découvert XPEnology, Korben vous fait les présentations.

Etant donné que j’ai déjà un serveur debian pour usage multimédias et domotique, autant virtualiser XPenology sur ce même serveur !

Excusez le manque d’analyse et d’informations de XPenology, DSM, et autre outils relatfis au NAS. J’ai prodé ça le même jour où je l’ai découvert, on peut parler d’un coup de foudre.

Installer Xen sur Debian Wheezy

Les how-to sont nombreux sur la toile, parfois trop complet, alors je vais droit au but :

# apt-get install xen-tools xen-system-amd64 xen-hypervisor-4.1-amd64 bridge-utils

On déplace l’ordre de boot des kernel pour booter sur le kernet Xen.

# mv /etc/grub.d/10_linux /etc/grub.d/50_linux
# update-grub

Humm, avant tout, assurez vous d’avoir un processeur capable de virtualiser :

# egrep 'vmx|svn' /proc/cpuinfo

Si vous avez  au moins une ligne qui ressort avec les flags, c’est que votre CPU supporte la virtualisation.
Cependant, si vous avez pris de l’avance et exécutez cette commande en étant déjà sur un kernel Xen, le résultat attendu ne s’affichera pas.

Après un reboot, vous devriez vous retrouver sur un kernel Xen. Pour s’en assurer, faites appel à l’un de ses services 😉

# xm li
Name ID Mem VCPUs State Time(s)
Domain-0 0 2904 4 r----- 67.2

Point important, c’est bien beau d’avoir un processeur pouvant virtualiser, mais assurez vous que l’option est active dans le BIOS, pour cela executez cette commande :

# cat /sys/hypervisor/properties/capabilities
xen-3.0-x86_64 xen-3.0-x86_32p hvm-3.0-x86_32 hvm-3.0-x86_32p hvm-3.0-x86_64

Si la ligne ne fait pas référence à du HVM malgré que vous possédiez un procésseur avec technologie VT-x/AMD-V (flag vmx ou svn), c’est que l’option VT-x/AMD-V est désactivée dans le BIOS (je me suis fait avoir…).
Nous allons faire du HVM (Hardware Virtual Machine), et non de la paravirtualisation, plus de détails sur wikipedia.

Faire de la virtualisation complète, ça permet de ne pas se préoccuper de l’OS sur lequelle XPenology est ‘tiré’, ça a  l’air d’être du FreeBSD, bref je m’en moque.

Xen est installé, un peu de paramétrage, je vous affiche brut ma config :

# egrep -v '^(#|$)' /etc/xen/xend-config.sxp
(network-script network-bridge)
(vif-script vif-bridge)
(dom0-min-mem 196)
(enable-dom0-ballooning yes)
(total_available_memory 0)
(dom0-cpus 0)
(vnc-listen '0.0.0.0')
(vncpasswd '')
(keymap 'fr')

Le fichier de configuration tel quel me permet de fonctionner en mode bridge, et de la possibilité de me connecter en VNC à la machine virtuelle.
De nombreuses possibilités avec Xen, j’adore cet outil, comme dit au début, je vais droit au but, mais sachez qu’il possible de faire énormement avec Xen.

Viritualisation de XPenology

Voila le vif du sujet !
Du peu que j’ai regardé au sujet de XPenology, c’est qu’il s’installe sur une petite partie d’un disque du NAS, pour ma part, pour plus de souplesse, je préfère dédié un disque au fonctionnement de XPenology. Je commence par lui créer un disque de 8Go, au format fichier:

# dd if=/dev/zero of=/srv/xpenology.img bs=1 count=1 seek=8G

Pourquoi 8Go ? Parce que j’ai lu sur xpenology.com/forum qu’il fallait au minium 5Go. Certains parlent de 100Go.
J’admet ne pas avoir tout suivi sur l’emplacement du system d’XPenoloy, où est-ce qu’il se loge, la place qu’il prend, comment il gère le changement de disque.

Autrement, je dédie un disque physique au XPenology.
Dans mon cas /dev/sdd, un disque de 2To.
Cependant, j’ai constaté que je devais partitionner mon disque pour que DSM puisse créer un volume.
J’ai donc crée une partition primaire de 2To -> /dev/sdd1, non formatée, DSM s’en chargera.

Donc 2 supports :
– 1 disque image pour le système.
– 1 disque (ou partition il faudrait dire) de données pour les enregistrements, etc…

Il faut télécharger et mettre sur le serveur, l’image de boot de XPenology, que l’on trouve ici:
http://www.xpenology.nl/boot-images/
→ Gnoboot-10.5 DSM 5.0-4458 X64
Ou bien :
→ Nanoboot-5.0.3.2 DSM 5.0-45XX X64

Gnoboot :
Il a la réputation d’être stable et performant surtout sous environnement virtualisé.
Aucune mise à jour n’a eu lieu depuis longtemps : Aïe !
Les mises à jour de synology se font avec des pincettes, tant que Gnoboot n’évolue pas, peu de possibilités d’upgrader la version 4558.

Nanoboot :
C’est suivi et recommandé par la communauté.
Normalement performant, mais peu s’améliorer ou se dégrader selon la release utilisée.
Les mises à jour de synology se font généralement sans soucis, attendre quelques jours après une nouvelle release pour voir si un fix/problème fait son apparition.

Je stock l’iso dans /root/

Pour créer une machine virtuelle Xen, il faut crée un fichier .cfg
Ma machine virtuelle s’appelle synocam, mon fichier est donc /etc/xen/synocam.cfg et il contient ceci :

Dans le cas d’utilisation de GnoBoot :

kernel = '/usr/lib/xen-4.1/boot/hvmloader'
builder = 'hvm'
memory = '1024'
device_model='/usr/lib/xen-4.1/bin/qemu-dm'
# Disks
disk = [ 'file:/srv/xpenology.img,hda,w','phy:/dev/sdd1,hdb,w','file:/root/gnoboot_105_xpenology_5.0-4458_bootloader_X64_by_Poechi.iso,hdc:cdrom,r' ]
# Hostname
name = 'synocam'
# Networking
vif = [ 'mac=00:11:11:76:75:74,bridge=eth0' ]
# Behaviour
boot='dc'
# VNC
vnc=1
vncviewer=1
vncpasswd=''
sdl=0
usbdevice='tablet'
# Misc
localtime=1
# On fail
on_poweroff='destroy'
on_reboot='restart'
on_crash='restart'

Dans le cas d’une utilisation de NanoBoot, la configuration du domU change un peu :

kernel = '/usr/lib/xen-4.1/boot/hvmloader'
builder = 'hvm'
memory = '1024'
device_model='/usr/lib/xen-4.1/bin/qemu-dm'
vcpu=2
# Disks
disk = [ 'file:/srv/xpenology.img,hda,w','phy:/dev/sdd1,hdb,w','file:/root/NB_x64_5032_DSM_50-4528_Xpenology_nl.iso,hdc:cdrom,r' ]
# Hostname
name = 'synocam'
# Networking
vif = [ 'model=e1000,bridge=xenbr0,mac=00:11:11:76:75:74']
# Behaviour
boot='dc'
# VNC
vnc=1
vncviewer=1
vncpasswd=''
sdl=0
usbdevice='tablet'
# Misc
acpi = '1'
apic = '1'
localtime=1
viridian=1
xen_platform_pci=1
# On fail
on_poweroff='destroy'
on_reboot='restart'
on_crash='restart'

La conf est sortie d’un tiroir, d’un serveur Xen en prod virtualisant du Windows 2008 R2.
L’adresse MAC est modifiée pour simuler un NAS Synology sur le réseau afin d’être détecté par l’assistant Synology.
Le driver virtuel e1000 est utilisé par nanoboot, car le driver par defaut realtek r8169 n’est pas compilé dans nanoboot.
Elle doit commencer par 00:11:11:    . Le reste est inventé.
file:/srv/xpenology.img,hda,w correspond à mon disque image de 8Go, en tant que disque primaire, accès en écriture.
phy:/dev/sdd1,hdb,w correspond à mon disque/partition de 2To, en tant que disque secondaire, accès en écriture.
file:/root/gnoboot_105_xpenology_5.0-4458_bootloader_X64_by_Poechi.iso,hdc:cdrom,r correspond au bootloader de Xpenology, simulé dans un lecteur CD, accès en lecteur seul.
boot=’dc’ permet à la machine virtuelle de booter sur le CD.
memory = ‘1024’ peut être augmenté.
Le nombre de CPU dédié à cette machine est de 1, le nombre peut être adapté avec l’option vcpus=2 pour 2 CPUs dédiés par exemple.
Pour le disque dédié également, vous aurez la possibilité de jongler entre disques physiques, raid soft, raid hard, LVM, laisser DSM gérer ou ajoutant simplement des disques, ou gérer vous en amont les disques que vous incluez dans la machine virtuelle. Voir même le tromper en montant un partage d’une autre machine. J’apprécie cette souplesse !

Voila pour l’essentiel de la config de la machine virtuelle.

Installation de XPenology

A  ce stade, la configuration de Xen et le fichier de configuration de la machine virtuelle devraient permettre de démarrer celle-ci sans erreur.
Si quelque chose manque, Xen vous le fera très vite savoir, c’est tout bon ou rien. Préparez votre client VNC, ça va aller très vite.

Pour démarrer la VM :

# xm create synocam.cfg

Puis ouvrez vite votre client VNC pour vous connecter au serveur, en indiquant le nom où l’ip du serveur Xen.
Vous devriez voir assez rapidement le menu de selection du bootloader GnoBoot.

Menu grub de Gnoboot

Menu grub de Gnoboot

Sélectionnez la seconde ligne Install/Upgrade.
Laissez booter, souriez, ça avance !

Boot de GnoBoot

Boot de GnoBoot

Lorsque le boot est installé, lancez sur votre PC l’utilitaire Synology Assistant.
Il devrait voir votre faux NAS, faite un clic droit > Installer.
Un wizzard vous guide dans l’installation et vous demande un fichier .pat.
Vous pourrez télécharger le  fichier .pat sur le support de Synology :
http://download.synology.com/download/DSM/5.0/4458/
URL et fichier PAT à adapter selon la version que vous cherchez à installer, en rapport avec GnoBoot.
Dans notre cas DSM 5 version 4458 pour le NAS DS3612xs.

Installation en cours par Synology Assistant

Installation en cours par Synology Assistant

♫ Et ça continue encore et encore, c’est que le début d’accord d’accord  ♫

Installation terminez ! Enjoy !

Installation terminez ! Enjoy !

Connectez-vous dés à présent en HTTP à votre nouveau NAS entièrement virtualisé sous XEN.
DSM vous aura crée un volume sur votre petit disque de 8Go, vous pourrez supprimer ce volume pour n’utiliser que le disque dédié.
Gérez vos volumes, changez votre mot de passe, le nom de machine, installez vos paquets : Surveillance Station.

Capture d'écran de DSM sous Xen

Capture d’écran de DSM sous Xen

Pour terminer

J’utilise le SNMP sur DSM pour les graphs munin, sur DSM : Panneau de configuration > Terminal & SNMP; SNMP v2 avec communauté « public« .
Un petit retour sur notre Debian.
Ajout d’un node munin :

# munin-node-configure --shell -snmp synocam

Executez ce que vous avez envie de grapher.
synocam étant le hostname de mon NAS Synology sur mon réseau, à adapter.

Vous pourrez voir l’utilisation des ressources de votre XEN grâce à la commande xm top.

xm top pour voir l'utilisation des ressources

« xm top » pour voir l’utilisation des ressources

Démarrage automatique de la VM avec le serveur Xen :

# mkdir /etc/xen/auto/
# cd /etc/xen/auto/
# ln -s /etc/xen/synocam.cfg .

Pour éteindre votre NAS :

# xm shutdown synocam

Et voila, une très bonne solution à bon gout pour profiter de Surveillance Station.
Je n’ai pas lancé de benchmark sur le bouzin, mon benchmark va surtout se faire au fur et à mesure des ajouts de caméras.

Posté dans NVR, Sysadmin, Web, Xen | 8 Commentaires

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&amp;ts=1334692716&amp;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&amp;ts=1334739400&amp;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&amp;ts=1361557458&amp;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 !

Posté dans Debian, KNX, php, Web | 48 Commentaires

Graphique de températures 1-wire avec pChart2

Il y’a quelques temps, j’ai abordé le sujet de la réalisation de graphiques de températures pour munin.
La base étant  l’exploitation des sondes 1-Wire sur une machine Debian et l’utilisation en PHP.
J’ai également parlé de la mise en base de données MySQL des valeurs du bus OneWire.

Un des buts de la mise en place des valeurs dans une base, est de pouvoir générer des graphiques personnalisés sur une durée et/ou une sonde spécifique.

Jusqu’à présent, j’avais une préférence pour les librairies Artichow, mais le projet a été arrêté.
Je me suis tourné vers pChart v2 : http://www.pchart.net

Ce que j’apprécie particulièrement, c’est la sandbox, sa documentation complète,ainsi que ses nombreuses possibilités !

Sandbox pChart

Sandbox pChart

En partant de mes derniers articles, j’ai repris un code généré en sandbox et adapté pour 24h de données 1-wire :
Script encore une fois, à adapter, je n’ai pas fait appel à mes includes pour mieux comprendre le principe du script.

<?php
ini_set('display_errors','Off');

// Préfixe de toutes les tables du site
$MYSQLHOST = "localhost";
$MYSQLLOGIN = "root";
$MYSQLPWD = "toto";
$MYSQLBASE = "1wire";
// Conection avec la base de données mysql
$db = mysql_connect($MYSQLHOST, $MYSQLLOGIN, $MYSQLPWD);
$mysql=mysql_select_db($MYSQLBASE,$db);

function OWSensorList($type=null) {
 $OWSensorList = array();
 $cpt = 0;
 if ($type == 'temperature') $req_owlist = "SELECT * FROM 1wire_sensor WHERE 1wire_sensor_type='temperature' ORDER BY 1wire_sensor_id ASC";
 else $req_owlist = "SELECT * FROM 1wire_sensor WHERE 1 ORDER BY 1wire_sensor_id ASC";
 $qur_owlist = mysql_query($req_owlist) or die(mysql_error());
 while($dat_owlist = mysql_fetch_array($qur_owlist)) {
 $OWSensorList[$cpt]['sensor_id'] = $dat_owlist['1wire_sensor_id'];
 $OWSensorList[$cpt]['sensor_enable'] = $dat_owlist['1wire_sensor_enable'];
 $OWSensorList[$cpt]['sensor_type'] = $dat_owlist['1wire_sensor_type'];
 $OWSensorList[$cpt]['sensor_family'] = $dat_owlist['1wire_sensor_family'];
 $OWSensorList[$cpt]['sensor_idaddress'] = $dat_owlist['1wire_sensor_idaddress'];
 $OWSensorList[$cpt]['sensor_comment'] = $dat_owlist['1wire_sensor_comment'];
 $cpt++;
 }
 return $OWSensorList;
}

// Retourne juste l'heure ou les minutes de YYYY-MM-DD H:i:s
function SplitTime($datemysql,$type) {
 list($date, $time) = explode(" ", $datemysql);
 list($year, $month, $day) = explode("-", $date);
 list($hour, $min, $sec) = explode(":", $time);
 if($type == 'min') return $min;
 if($type == 'hour') return $hour;
}

function OWFetch24HData($sensor_id) {
 $OWFetch24HData = array();
 $cpt = 0;
 $req_owlist = "SELECT * FROM 1wire_data WHERE 1wire_data_sensor_id = ".$sensor_id." AND 1wire_data_timestamp BETWEEN DATE_ADD( NOW( ) , INTERVAL -1 DAY ) AND NOW( ) ORDER BY 1wire_data_id ASC";
 $qur_owlist = mysql_query($req_owlist) or die(mysql_error());
 while($dat_owlist = mysql_fetch_array($qur_owlist)) {
 $fetch_timestamp = $dat_owlist['1wire_data_timestamp'];
 $fetch_minute = SplitTime($fetch_timestamp,'min');
 if($fetch_minute != '00') $OWFetch24HData['data_timestamp'][$cpt] = '';
 elseif($fetch_minute == '00') $OWFetch24HData['data_timestamp'][$cpt] = SplitTime($fetch_timestamp,'hour')."h";
 $OWFetch24HData['data_value'][$cpt] = $dat_owlist['1wire_data_value'];
 $cpt++;
 }
 return $OWFetch24HData;
}

include("lib/pChart2.1.3/class/pData.class.php");
include("lib/pChart2.1.3/class/pDraw.class.php");
include("lib/pChart2.1.3/class/pImage.class.php");

$myData = new pData();

$OWSensorList = OWSensorList('temperature');
for ($i = 0; $i < sizeof($OWSensorList); $i++) {
 if($OWSensorList[$i]['sensor_enable']) {
 $OWFetch24HData = OWFetch24HData($OWSensorList[$i]['sensor_id']);
 $myData->addPoints($OWFetch24HData['data_value'],$OWSensorList[$i]['sensor_id']);
 $myData->setSerieDescription($OWSensorList[$i]['sensor_id'],$OWSensorList[$i]['sensor_comment']);
 $myData->setSerieOnAxis($OWSensorList[$i]['sensor_id'],0);
 }
}

$myData->addPoints($OWFetch24HData['data_timestamp'],"Absissa");
$myData->setAbscissa("Absissa");

$myData->setAxisPosition(0,AXIS_POSITION_LEFT);
$myData->setAxisName(0,"Temp&eacute;ratures");
$myData->setAxisUnit(0,"°C");

$myPicture = new pImage(1000,400,$myData);
$myPicture->setShadow(TRUE,array("X"=>1,"Y"=>1,"R"=>50,"G"=>50,"B"=>50,"Alpha"=>20));

$myPicture->setFontProperties(array("FontName"=>"fonts/Forgotte.ttf","FontSize"=>18));
$TextSettings = array("Align"=>TEXT_ALIGN_MIDDLEMIDDLE
, "R"=>0, "G"=>0, "B"=>0);
$myPicture->drawText(500,25,"Températures",$TextSettings);

$myPicture->setShadow(FALSE);
$myPicture->setGraphArea(75,50,975,360);
$myPicture->setFontProperties(array("R"=>0,"G"=>0,"B"=>0,"FontName"=>"fonts/Bedizen.ttf","FontSize"=>10));

$Settings = array("Pos"=>SCALE_POS_LEFTRIGHT
, "Mode"=>SCALE_MODE_FLOATING
, "LabelingMethod"=>LABELING_ALL
, "GridR"=>255, "GridG"=>255, "GridB"=>255, "GridAlpha"=>50, "TickR"=>0, "TickG"=>0, "TickB"=>0, "TickAlpha"=>50, "LabelRotation"=>0, "CycleBackground"=>1, "DrawXLines"=>1, "DrawSubTicks"=>1, "SubTickR"=>255, "SubTickG"=>0, "SubTickB"=>0, "SubTickAlpha"=>50, "DrawYLines"=>ALL);
$myPicture->drawScale($Settings);

$myPicture->setShadow(TRUE,array("X"=>1,"Y"=>1,"R"=>50,"G"=>50,"B"=>50,"Alpha"=>10));

$Config = "";
$myPicture->drawSplineChart($Config);

$Config = array("FontR"=>0, "FontG"=>0, "FontB"=>0, "FontName"=>"fonts/Forgotte.ttf", "FontSize"=>12, "Margin"=>6, "Alpha"=>30, "BoxSize"=>5, "Style"=>LEGEND_NOBORDER
, "Mode"=>LEGEND_HORIZONTAL
, "Family"=>LEGEND_FAMILY_LINE
);
$myPicture->drawLegend(837,16,$Config);

$myPicture->stroke();
?>

Il faut au moins 1h de données, résultat :

Graphique 1-wire avec librairie pChart

Graphique 1-wire avec librairie pChart

Comme d’habitude, il est nécessaire d’adapter, inclure, configurer, à chacun comme bon lui semble. Ça reste un exemple, ou une base de travail.

En tout cas, parmi les différentes libraries PHP pour génerer des graphiques, pChart2 est mon coup de coeur, même si le projet n’a pas bougé depuis septembre 2011, il est suffisament abouti.

Posté dans 1-wire, php, Web | 4 Commentaires

Interagir en jQuery/Ajax avec le bus KNX

Une semaine après mon article sur un frontend php  KNX.
Je bosse sur une interface simple pour visualiser et contrôler des points lumineux.

Pour l’instant, je fais au plus simple, j’ai deux points lumineux :
– Une ampoule sur un TXA213.
– Un bandeau LED RGB contrôlé par OLA.

Je pourrais également paramétrer d’autres ampoules sur mes TXA207C, mais je fais aussi du On/Off avec le TXA213.

J’ai fait un petit screencast aussi simplet que mes scripts, à regarder en 720p.

Il est difficile d’entrer dans les détails du script, cependant, le fonctionnement principal est lié à mon article précédent sur le bus KNX et l’enregistrement des valeurs dans la base MySQL.

La nouveauté provient des scripts jQuery et Ajax pour le rafraîchissement toute les 0,5 secondes des éléments de la page.

Je change un élement à partir de la page web, j’utilise writegroup en php. (Voir mon article : Prise en main du KNX en php ).
Et continue de lire ma BDD, sans y écrire de changement, le frontend php s’en occupe.

Il y’a 3 scripts AJAX/PHP :

  • knx-get_light_value.php : me récupère simplement les valeurs dans la BDD selon le « nom système » écrit dans le div id html.
  • knx-set_light_toggle.php : me change l’état d’une lumière, On ou Off, rien de plus sur un EIS n° 1.
  • knx-set_light_value.php : plus complexe que le précédent script, peut gérer d’autres EIS.

Evidemment, si je change l’état d’une lumière depuis un bouton poussoir, le changement est visible sur la page (ce que je fais à 0:50).

Je partagerai volontiers les sources lorsque j’aurai amélioré le schlimblik en back-end.

Posté dans Debian, KNX, php, Web | 3 Commentaires

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" &gt; /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.

Posté dans Debian, php, Sysadmin, Web | 1 Commentaire

Insérer les valeurs 1-wire dans une base MySQL

J’ai déjà abordé la récupération des températures dans un graphique munin.
J’ai également abordé la récupération de ces mêmes valeurs en PHP.

Et maintenant, place à la récupération des valeurs pour l’insertion dans une base de données MySQL.
Le but étant de passer par le cron, d’avoir une table avec les sondes, une table avec les valeurs.

J’ai réalisé ce premier ensemble de scripts regroupant pas mal de require, c’est plus difficile à partager pour la compréhension et les explications mais j’espère que ce script restera sous cette forme pour mon usine à gaz définitive.

Structure de la base

Une première table avec les sondes, voici son squelette :

CREATE TABLE IF NOT EXISTS `1wire_sensor` (
  `1wire_sensor_id` int(3) NOT NULL AUTO_INCREMENT,
  `1wire_sensor_enable` enum('true','false') NOT NULL,
  `1wire_sensor_type` enum('temperature','presence','sensed.A') NOT NULL,
  `1wire_sensor_family` tinytext NOT NULL,
  `1wire_sensor_idaddress` tinytext NOT NULL,
  `1wire_sensor_comment` text NOT NULL,
  PRIMARY KEY (`1wire_sensor_id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

Une seconde table avec les valeurs, son squelette :

CREATE TABLE IF NOT EXISTS `1wire_data` (
  `1wire_data_id` int(11) NOT NULL AUTO_INCREMENT,
  `1wire_data_sensor_id` int(3) NOT NULL,
  `1wire_data_timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `1wire_data_value` text NOT NULL,
  PRIMARY KEY (`1wire_data_id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

Le script automatique

Comme expliqué plus haut, difficile de partager un script complexe pour un besoin simple.
Je fais un « require » pour appeler un fichier de config, un autre pour la connexion MySQL, un dernier pour mes fonctions 1-wire.

Ce qui donne cet ensemble :

<?php
$pathconfig = "/var/www/domy";
require_once($pathconfig."/require/main.inc.php");
require_once(PATH."/require/database.inc.php");
require_once(PATH."/require/function-1wire.inc.php");

// Recup des données des sondes depuis la BDD
$req_sensor = "SELECT * FROM 1wire_sensor WHERE 1wire_sensor_enable IS TRUE";
$qur_sensor = mysql_query($req_sensor);
while($dat_sensor = mysql_fetch_array($qur_sensor)) {
 // Pour chaque sonde on constitue son adresse :
 $onewire_address = $dat_sensor['1wire_sensor_family'].".".$dat_sensor['1wire_sensor_idaddress'];
 // On teste sa présence sur le bus
 if(onewire_presence($onewire_address)) {
 debug("OUI ! ".$onewire_address." est présent sur le bus","cli");
 if ($dat_sensor['1wire_sensor_type'] == "presence") {
 debug("Seul sa presence sur le bus est nécessaire","cli");
 // on ecris dans la BDD qu'il est présent et on s'arrete là
 $value = 1;
 }
 elseif ($dat_sensor['1wire_sensor_type'] == "temperature" || $dat_sensor['1wire_sensor_type'] == "sensed.A") {
 // on récupère sa valeur, que ca soit la température ou l'état du PIO (DS2406)
 $value = onewire_read($onewire_address,$dat_sensor['1wire_sensor_type']);
 }
 }
 else {
 // Composant absent du bus
 debug ("NON ! ".$onewire_address." n'est pas présent sur le bus","cli");
 $value = 0;
 }

 // on écris le resultat dans la BDD
 $req_insdata = "INSERT INTO 1wire_data SET
 1wire_data_sensor_id = ".$dat_sensor['1wire_sensor_id'].",
 1wire_data_value = '".$value."'";
 mysql_query($req_insdata) or die ("Erreur à l'insertion des données 1-wire : ".mysql_error());
}

?>

J’espère que le principe de ce script php reste compréhensible avec les commentaires.

L’ensemble des fichiers sont disponible à la fin de l’article.

Le contenu de la base

Une fois la structure de la base établie ainsi que le script chargé de consulter les élements 1-wire, il reste à saisir les informations liés aux éléments 1-wire.
Pour cela, en plus d’avoir identifiant chaque sonde au moment de son câblage, je me sert de owhttpd pour récupérer l’ID  des sondes. ( http://ipduserveur:2121/ )

Fenêtre fermée, DS2401 présent dans owhttp

Fenêtre fermée, DS2401 présent dans owhttp

J’ai descidé de spliter en deux l’ID des sondes 1-wire.
Par exemple, pour 28.7B2DB5030000 :
– Famille : 28.
– ID : 7B2DB5030000.

Et j’utilise phpmyadmin pour inserer la 1re sonde de température ( 28.7B2DB5030000 ) afin d’obtenir ceci :

Sonde 1-wire dans la base MySQL via phpmyadmin

Sonde 1-wire dans la base MySQL via phpmyadmin

Ainsi de suite pour chaque sonde désirée, 1wire_sensor_id étant la clée primaire en AUTO_INCREMENT.

A terme, le but étant de créer une interface pour gérer l’ajout, l’édition et la supression de sonde plus facilement via une page développée dans ce sens.
Idem pour chaque famille de sonde : température / présence / PIO.

Execution

On peut faire un simple test :

# cd /var/www/1-wire/cron
# php fetch-1wire.php

Vous devriez obtenir les valeurs de température.
Si vous obtenez le message :

PHP Notice: Can’t create socket [ow://127.0.0.1:4304], errno: 111, error: Connection refused in /var/www/1-wire/require/ownet.php on line 205
PHP Notice: Can’t connect get#1 in /var/www/1-wire/require/ownet.php on line 388

Modifiez le /etc/owfs.conf pour remplacer :
server: port = localhost:4304
Par :
server: port = 4304

Redemarrez owserver et refaite le test.

Si tout est ok, l’execution se fait via le cron, tout simplement :

# vi /etc/cron.d/cron1wire
*/5 *   * * *   root    /usr/bin/php -q /var/www/1-wire/cron/fetch-1wire.php

Au bout de quelques heures :

Résultat  de l'insertion des températures 1-wire dans une base MySQL

Résultat de l’insertion des températures 1-wire dans une base MySQL

Télécharger

Télécharger l’archive

A adapter, bidouiller, comprendre, améliorer, etc…

Et bientôt la génération de beaux graphs à partir de ces valeurs.

Posté dans 1-wire, Debian, graphs, php | 5 Commentaires

Se passer de linknx et webknx2, une fausse bonne idée ?

Aujourd’hui, grande réflexion autour de linknx et knxweb2.
Je me suis attardé sur ces deux outils, en plus de leur découverte via une Live CD.

Les VM, rien de plus pratique pour essayer sans casser

Les VM, rien de plus pratique pour essayer sans casser

Le but étant, entre autres, de découvrir l’interface de knxweb2 (knxweb v1 sur le LiveCD), et comprendre la communication entre le click et l’ampoule.

KnxWeb2

C’est à peu près démystifié : knxweb2 tourne en AJAX/jQuery qui communique en XML à travers un savant mélange avec linknx.
C’est puissant ! Instantané.

Test de KNXWEB2

Test de KNXWEB2

Seul bémol, au jour d’aujourd’hui, je ne me vois pas capable de « maîtriser » linknx et knxweb2, car là dedans, je n’ai pas mes lots :
– Asterisk
– Notifry
– DMX
– 1-wire
– Alarme
– Teleinfo
– ZoneMinder (vidéo surveillance).

C’est très certainement insérable dans knxweb2, mais le temps que je prenne pour intégrer l’ensemble de mon cahier des charges, j’ai mieux fait de partir de zéro.
Je souhaite connaître au maximum le cheminement de mon usine à gaz pour l’adapter et la faire évoluer au grès des besoins.

Mon problème, c’est que jamais je n’arriverai à égaler la réactivité de knxweb2, mais je vais essayer de m’en approcher.

Linknx

J’ai continué l’approche sur linknx avec une page de test php, et les actions en rapports.
Ça permet de bien saisir la communication :
Clic → PHP → linknx → eibnetmux → knx → ampoule.
Mais au final, d’après ce script de test en php, ça revient à la même chose que :
Clic → PHP → Class PHP Eibnetmux → eibnetmux → knx → ampoule.

Pour linknx, la base utilise un fichier le config xml du paquet.
Pour PHP @ eibnetmux, la base utilise une base de données MySQL et des scripts.

C’est kif kif.
Entre la Class PHP KNX et l’utilisation de linknx par PHP, le fonctionnement est semblable : il y’a ouverture d’un socket pour lire et écrire les informations à la couche inférieure.

Donc pour la partie PHP → KNX, avec ou sans linknx, je ne m’évite pas du travail, ne me complique pas plus la tâche.

Pour conclure

Beaucoup de doutes et de questions.
Au jour d’aujourd’hui, je pense remplacer linknx par des scripts PHP et une base MySQL.
Ainsi que knxweb2 par une interface web perso, en sachant que je n’arriverai pas à égaler sa réactivité, mais je gagnerai en maîtrise, souplesse vis à vis de mon cahier des charges.

Posté dans Debian, KNX, php, Sysadmin | 5 Commentaires