Category Archives: Debian
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 :
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 :
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 :
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.
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 :
# su
# apt-get dist-upgrade
# apt-get install htop vim screen usbutils
Puis les choses sérieuses commecent, on branche le dongle DS1490 :
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.
Puis on modifie le fichier /etc/owfs.conf :
On commente la ligne 16 et décommente la ligne 19.
# ...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
[ 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. …
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 :
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.
# # 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 #
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 :
# 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 :
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 …
Et je créer mon script qui récupère les valeurs des sondes :
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(); ?>
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.
# cd /root/1-wire/require/
# cp /usr/share/php/OWNet/ownet.php .
Puis deux corrections sont nécessaire :
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 :
# 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 :
- Le cron lance le script /usr/local/bin/1wire-fetch.php
- Ce script utilise les fichiers stocké dans /root/1-wire pour se connecter au serveur MySQL distant et au bus 1-wire
- Le script consulte la table MySQL 1wire_sensor pour lister les sondes à intéroger sur le bus
- 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.
- Toujours ce script, ajoute les valeurs récupérées dans la table MySQL 1wire_data du serveur distant.
- Le script est terminé, il recommencera dans une heure.
- 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é :
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 :
[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 :
[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 :
# 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.
# 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.
<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 :
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.
Une interface web pour contrôler le bus KNX sur Raspberry Pi
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.
# 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.
Téléchargement et installation de Zlogger
Quelques libraires nécessaires pour l’installation :
Zlogger 1.5.0 est nécessaire au fonctionnement d’eibnetmux.
# wget "http://downloads.sourceforge.net/project/zlogger/zlogger/1.5.0/zlogger-1.5.0.tar.bz2?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fzlogger%2F&ts=1334692716&use_mirror=freefr" -O zlogger-1.5.0.tar.bz2
# tar xfv zlogger-1.5.0.tar.bz2
# cd zlogger-1.5.0
# ./configure --with-plugins --enable-pth-plugins
# make
# make install
# ldconfig
Télechargement et installation d’eibnetmux
Plus de détails dans l’article dédié à eibnetmux et eibd.
La version d’eibnetmux est la dernière disponible selon le site de son auteur via sourceforge.
Pour utliser eibnetmux avec php, celui-ci est nécessaire*.
* Merci à jjay pour ces infos en commentaires
Puis on installe eibnetmux
# wget "http://downloads.sourceforge.net/project/eibnetmux/eibnetmux/2.0.1/eibnetmux-2.0.1.tar.gz?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Feibnetmux%2Ffiles%2Feibnetmux%2F2.0.1%2F&ts=1334739400&use_mirror=netcologne" -O eibnetmux-2.0.1.tar.gz
# tar zxvf eibnetmux-2.0.1.tar.gz
# cd eibnetmux-2.0.1
# ./configure --enable-php
# make
# make install
# ldconfig
On récupère et installe le « Sample client applications » ; ce package contient les fichiers PHP nécessaires pour un lire et écrire sur le bus KNX..
Il contient également les commandes de bases équivalentes à groupread, groupwrite, groupswrite, groupsocketlisten, etc…
# wget "http://downloads.sourceforge.net/project/eibnetmux/Sample%20client%20applications/1.7.1/eibnetmuxclientsamples-1.7.1.tar.gz?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Feibnetmux%2Ffiles%2FSample%2520client%2520applications%2F1.7.1%2F&ts=1361557458&use_mirror=freefr" -O eibnetmuxclientsamples-1.7.1.tar
# tar xvf eibnetmuxclientsamples-1.7.1.tar
# cd eibnetmuxclientsamples-1.7.1
Pause !
Une grosse modification apportée à un fichier, il s’agit du fichier eibtrace/eibtrace.c :
J’ai modifié le fichier pour retourner à une syntaxe plus simple à traiter :
eibtrace.c
J’ai également ajouté un « fflush(stdout);« , sans cet ajout, eibtrace ne parvient pas à rediriger stdout vers un fichier. Et comme le C++ et moi, ça fait deux, je me contente de cette rustine…
Et on compile les scripts d’exemples d’eibnetmux.
# 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 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.
# 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 :
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 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.
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 :
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.
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
# 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.
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.
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 :
# 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)
# 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.
[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:
Et il vous pond un script init.d ! magique !
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 :
Logrotate
Coté logrotate, l’on crée un script dans le dossier /etc/logrotate.d/ qui sera automatiquement traité quotidiennement, généralement à 06h25.
Contient :
/var/log/knx-tail2mysql.log { weekly missingok rotate 12 compress delaycompress notifempty sharedscripts postrotate /etc/init.d/knx-tail2mysql restart > /dev/null endscript }
-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 :
[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.
# 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.
+-------------------+-------------+
| 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 !
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.
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.
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é ».
Ca marche bien !
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 ».
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.
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.
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
raspberry-domolio-knx.img : 7,42 Go
MD5 : 81a8df6724fea6c8916f6a8e814b908f
Have fun !
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.
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.
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 :
L’extention activée pour php5 pour le cli :
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.
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.
Communication entre le bus KNX et un serveur Linux
Jusqu’à présent, j’ai découvert comment envoyer des ordres ou obtenir une valeur avec mon bus KNX.
Que ce soit en PHP ou par ligne de commande.
Mon souhait est de pouvoir faire l’inverse : depuis le bus KNX, de donner un autre à mon serveur.
Plusieurs applications possibles, en voici deux concrètes :
- Allumer/couper les lumières d’ambiance DMX, changer les couleurs.
- Allumer/couper le wifi.
C’est sans limite en fait.
Du moment où j’envois une commande (boutton poussoir) au serveur celui-ci peut être interprété comme bon me semble.
Je passe de l’électrique de puissance à l’informatique classique.
Comment ça marche ?
J’ai essayé de faire un schéma :
Coté programmation, j’ai crée un groupe d’adresses 0/0/10 qui ne contient aucune action, le groupe est lié à l’interrupteur bouton poussoir.
Notre serveur, équipé d’eibnetmux (lien vers son installation) utilise grouplisten pour écouter ce qu’il se passe sur le bus KNX pour un Groupe d’Adresse spécifié, ici le GA 0/0/10.
La fonction grouplisten est exécutée en même temps que le démarrage d’eibnetmux, j’ai rajouté quelques lignes à partir de mon script initial.
La sortie de cette commande est redirigée vers un fichier.
# # 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/grouplisten ip:127.0.0.1 0/0/10 > /tmp/knx_0-0-10 & echo " done" }
Je vais me servir de ce fichier pour surveiller les changements d’état.
J’utilise l’outil logtail pour vérifier les nouveaux évènements.
Pour cet exemple, je souhaite que l’utilisation du bouton poussoir allume mon bandeau RGB, dont j’ai récemment effectué l’installation.
# chmod +x /usr/local/bin/knx2dmx
# vi /usr/local/bin/knx2dmx
#!/bin/bash lockfile=/tmp/knx2dmx.run if [[ -f $lockfile ]] ; then echo "knx2dmx déjà en cours d'exécution ou supprimer le fichier $lockfile" exit fi touch $lockfile groupaddress=$1 file=/tmp/knx_$groupaddress bouclette="yes" # Boucle sans fin while [ $bouclette = "yes" ] do { # Mais on est pas à 1 seconde près sleep 1 value=`/usr/sbin/logtail -f -o $file | grep "Write" | cut -d" " -f4` if [ "$value" == 01 ] then # Ici j'allume mon bandeau RGB, mais toute autre action est envisageable /usr/local/bin/dmxchangecolorfader.py 255 25 100 elif [ "$value" == 00 ] then # Je l'éteinds /usr/local/bin/dmxchangecolor.py 0 0 0 fi } done
Et celui là, je le lance dans le cron.
*/10 * * * * root /usr/local/bin/knx2dmx 0-0-10
Donc si mon script, via logtail, détecte que l’adresse de groupe 0/0/10 KNX passe à 1, il allume le strip RGB DMX.
A l’inverse, s’il détecte un 0, il l’éteint.
A titre de supplément, le script dmxchangecolor.py est celui abordé dans l’installation d’OLA.
#!/usr/bin/python import sys, array from ola.ClientWrapper import ClientWrapper def DmxSent(state): wrapper.Stop() universe = 1 data = array.array('B') data.append(int(sys.argv[1])) data.append(int(sys.argv[2])) data.append(int(sys.argv[3])) wrapper = ClientWrapper() client = wrapper.Client() client.SendDmx(universe, data, DmxSent) wrapper.Run()
Et le script dmxchangecolorfader.py provient aussi de la page wiki d’OLA.
Que j’ai beaucoup bidouillé car je ne sais pas codé en python :
Je n’en suis pas fier, surtout qu’il ne faut pas mettre de valeur à 0.
#!/usr/bin/python import sys, array from ola.ClientWrapper import ClientWrapper red_arg = int(sys.argv[1]) green_arg = int(sys.argv[2]) blue_arg = int(sys.argv[3]) red = 0 green = 0 blue = 0 wrapper = None loop_count = 0 TICK_INTERVAL = 10 # in ms def DmxSent(state): if not state.Succeeded(): wrapper.Stop() def SendDMXFrame(): # schdule a function call in 100ms # we do this first in case the frame computation takes a long time. wrapper.AddEvent(TICK_INTERVAL, SendDMXFrame) # compute frame here data = array.array('B') global loop_count global red global green global blue if red == (red_arg-1): data.append(red_arg) else: red = loop_count % red_arg data.append(red) if green == (green_arg-1): data.append(green_arg) else: green = loop_count % green_arg data.append(green) if blue == (blue_arg-1): data.append(blue_arg) else: blue = loop_count % blue_arg data.append(blue) loop_count += 1 if (red == (red_arg-1)) and (green == (green_arg-1)) and (blue == (blue_arg-1)): exit() # send wrapper.Client().SendDmx(1, data, DmxSent) wrapper = ClientWrapper() wrapper.AddEvent(TICK_INTERVAL, SendDMXFrame) wrapper.Run()
Résultat
Pour aller plus loin avec un frontend php
Et voila ! Une bonne base pour faire interagir le serveur depuis le bus KNX.
Je pourrai rajouter autant de grouplisten que necessaire, mais dans une certaine mesure, il sera certainement préférable d’écouter tout ce qu’il se passe sur le bus via groupsocketlisten.
Avec un principe un petit peu différent :
Dans ce cas, groupsocketlisten écoute tout ce qu’il se passe sur le bus.
Un script PHP est à l’écoute des informations, certainement sur le même principe du logtail.
Il filtre et remplit des informations dans la base de données MySQL.
Cette partie là est intéressante, si par exemple, j’allume une lumière derrière un variateur TXA213, groupsocketlisten y voit défiler deux informations :
Write from 0.2.189 to 0/0/1: 01 Write from 1.1.2 to 0/0/7: 01 Write from 1.1.2 to 0/0/8: AF
Ici, j’ai allumé ma lumière grâce à un « groupswrite ip:127.0.0.1 0/0/1 1 », je le vois sur mon bus (0.2.189).
Et deux autres informations utiles y circulent :
– 0/0/7 correspond à l’indicateur d’état, à 01 donc ma lampe est allumée.
– 0/0/8 correspond à l’indicateur de la valeure d’éclairement en hexadécimale sur FF (255).
Ici la valeur hexa AF (FF étant le max) correspond à 175 en décimale (255 étant le max).
Ainsi, avec un base de données bien pensée, il est possible de faire un filtre intelligent : un type d’objet c’est tant et tant d’information à récolter avec tels et tels groupes d’adresses.
On peut pousser le bouchon plus loin, c’est ce « frontend » PHP qui pourrait exécuter des actions sur le système tel que l’allumage DMX.
Une fois la BDD avec des informations actualisées, il ne reste plus qu’à les exploiter à travers une page web.
Une solution qui me semble bonne pour les fondations d’une bonne usine à gaz.
Je vais commencer quelque chose dans ce principe pour un prochain article.
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/ )
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 :
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 :
# 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 :
*/5 * * * * root /usr/bin/php -q /var/www/1-wire/cron/fetch-1wire.php
Au bout de quelques heures :
Télécharger
A adapter, bidouiller, comprendre, améliorer, etc…
Et bientôt la génération de beaux graphs à partir de ces valeurs.
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.
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é.
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.
Interroger les sondes 1-wire par un script PHP
Un point que je n’avais pour l’instant pas abordé dans mes différents articles sur le 1-wire :
- Premiers pas avec le 1-wire et owserver sous Debian Wheezy
- Découvertes des sondes DS18B20 et DS18B20PAR
- Capteur d’ouverture de fenêtres avec un DS2406
- Capteur d’ouverture de fenêtres avec un DS2401
Je n’ai pas abordé le contrôle du 1-wire en PHP.
Pour faire quoi ?
Une grosse usine à gaz !
Utiliser PHP pour interroger les sondes 1-wire, c’est tout simplement ce qu’il y’a de plus logique dans mon projet, vue que je souhaite une interface à la knxweb2 mais home made.
L’affichage d’une température sur un plan et toute autre utilisation des mesures se fera par PHP.
Quels paquets php à installer ?
Comme vu dans la phase de découverte, owserver est désormais disponible en package compilé pour Debian Wheezy.
Pour les librairies PHP, il existe deux paquets aux noms semblables : libow-php5 et libownet-php.
Quelle différence ? La question a été posée au contributeur du package, voici sa réponse :
libownet-php ne peut que communiquer avec owserver, qui, lui-même pourra communiquer avec d’autres owserver et/ou matériel 1-wire.
libow-php5 est un wrapper autour de la bibliothèque C qui permet de communiquer directement avec le matériel 1-wire.
Alors on installe libownet-php.
Ce qui a pour effet d’installer un fichier php de class au chemin suivant :
/usr/share/php/OWNet/ownet.php
Une page d’exemple est disponible au chemin suivant :
/usr/share/doc/libownet-php/examples/ownet_example.php
Cet exemple fait référence à un fichier bcadd.php, il s’agit d’un hack car certaines versions distribuées de php ne permettent pas l’utilisation de cette fonction, fournir cette fonction par un include permet de contourner le problème. Evidemment, sous Debian Wheezy hébergement maison, ce qu’il y’a de plus classique, pas de problème avec bcadd, donc le « require » ne sera pas utilisé.
Récupérer des valeurs en PHP
Pour ce test, j’ai placé sur mon bus 1-wire :
- 1x DS18B20 : sonde de température à l’extérieur.
- 1x DS18B20+PAR : sonde de température à l’intérieur.
- 1x DS2406 : capteur d’ouverture de fenêtre.
- 1x DS2401 : capteur d’ouverture de fenêtre.
L’ensemble des fonctions ownet php sont visibles sur la page du projet :
http://owfs.org/index.php?page=ownet-php
Je récupère le fichier ownet.php, sans quoi on obtient forcément l’erreur suivante :
PHP Fatal error: Class 'OWNet' not found
Et mon index.php
<?php require "ownet.php"; $ow=new OWNet("tcp://127.0.0.1:4304"); ?> Température extérieure : <? $temp_ext = $ow->read("/28.EA54B5030000/temperature"); // Décommenter la ligne suivante pour avoir les détails du composant //var_dump($ow->dir("/28.EA54B5030000",OWNET_MSG_READ,true)); echo $temp_ext; // ------------------ ?> <br /> Température intérieure : <?php $temp_int = $ow->get("/28.919277030000/temperature",OWNET_MSG_READ,false); // Décommenter la ligne suivante pour avoir toutes les infos sur la température //var_dump($ow->get("/28.919277030000/temperature",OWNET_MSG_READ,true)); echo $temp_int; // ------------------ ?> <br /> Fenêtre DS2406 : <?php $fenetre_ds2406 = $ow->read("/12.D0457D000000/sensed.A"); // Décommenter la ligne suivant pour avoir toutes les infos sur le PIO A //var_dump($ow->get("/12.D0457D000000/sensed.A",OWNET_MSG_READ,true)); echo ($fenetre_ds2406)?"ouverte":"fermée"; // ------------------ ?> <br /> Fenêtre DS2401 : <?php $fenetre_ds2406 = $ow->presence("/01.DC4343140000"); // Décommencer la ligne suivante pour avoir les détails, minimaliste car c'est une présence //var_dump($ow->dir("/01.DC4343140000",OWNET_MSG_READ,true)); echo ($fenetre_ds2406)?"fermée":"ouverte"; // ------------------ ?> <br />
J’ai laissé les lignes var_dump commentées, elles peuvent servir de débogage.
Le port 4304 à adapter selon votre serveur owserver ( /etc/owfs.conf ).
A noter, comme vue dans mon article sur le DS2406, sensed.A retourne un 1 lorsque l’ILS est ouvert, 0 lorsque qu’un aimant est à proximité, ce qui pourrait porter à confusion.
Read et Get sont sur un bateau
Les read et le get, syntaxé comme ceci, apportent le même résultat.
$ow->get("/28.919277030000/temperature",OWNET_MSG_READ,false); $ow->read("/28.919277030000/temperature");
D’après la doc de owfs.org le get parcourt chaque valeur de l’élément désiré et y applique une fonction/constante.
Tandis que le read, récupère la valeur demandée, point final.
Dans mes tests, et pour une requête de température il s’avère que le get ne retourne pas toujours de valeurs.
Il semble fonctionner à un niveau plus « bas » que le read.
Donc la fonction read est à privilégier.
Et maintenant ?
L’insertion des données dans une base MySQL, pour un prochain article.
Installation d’OLA et prise en main du DMX à travers python & php
Maintenant que notre contrôleur compatible Open DMX, nous allons pouvoir l’exploiter logicielement.
Mon but étant de pouvoir changer la couleur (ce qui reviens à allumer/éteindre) en quelques clics ; ainsi que de voir ce qu’il est possible de faire en ligne de commande et idéalement de changer la couleur depuis une page web.
La platine de test DMX
Mon installation est composée de :
- DMX USB PC V3 (Contrôleur USB / DMX) → 36,50 €.
- Câble XLR de récup → 0 €.
- Décodeur DMX RGB → 23,08 €.
- Alim 12V de récup → 0 €.
- Câble pour connecter les bandeaux RGB → 1,50 €.
- 2x Bandeau RGB 12V 5050 30cm → 1,35 € / pièce.
- Bouchon DMX , livré avec le DMX USB PC → 0 €.
Soit environ 67 € le tout, frais de ports inclus.
Un petit schéma de principe :
En phase final, je compte remplacer le XLR par de l’Ethernet et le strip RGB par des spots encastrables LED RGB de ce style :
Le revendeur est l’ancien Eurolite.de, devenu Steinigke Showtechnic : la fiche produit du spot encastable LED RGB 10mm.
Installation de Open Lighting Architecture (OLA)
Toujours depuis une Debian Wheezy, on commence par installer différents paquets qui seront nécessaire pour la compilation.
# ldconfig
Et on récupère l’archive d’OLA.
# wget http://linux-lighting.googlecode.com/files/ola-0.8.18.tar.gz
# tar zxvf ola-0.8.18.tar.gz
# cd ola-0.8.18
# ./configure --enable-python-libs --enable-http
Le « enable-python-libs » permettra d’envoyer des commandes DMX à partir d’un script python.
Le « enable-http » permettra d’utiliser de tester ola à travers une page http.
# make install
# ldconfig
Et voila ! C’est installé.
Ola exige d’être démarré avec un utilisateur non root.
# su ola
Avant cette première exécution, il est important de veiller à ce que le contrôleur USB/DMX soit reconnu en tant que /dev/dmx0.
Le 3 (/4) correspond au niveau de verbosity.
Vous devriez voir au moins ces lignes apparaîtrent :
PluginManager.cpp:74: Trying to start Enttec Open DMX DeviceManager.cpp:111: Installed device: OpenDmx USB Device:6-0 PluginManager.cpp:78: Started Enttec Open DMX
Si vous avez installez libmicrohttpd5, vous devriez pouvoir accéder à la page http://ip_du_serveur_ola:9090 .
Et configurer votre fixture aka « universe« .
J’ai attribué au contrôleur RGB l’adressage DMX 1.
Dont la notice a été scannée.
Dans l’onglet Console, je peux modifier la valeur de mes trois canaux RGB et donc chacune des trois couleurs.
Ce qui donne :
Youpi !
De quoi être tranquille au démarrage de la machine, j’ai pondu ce petit init.d script pour ola à partir du skeleton.
#! /bin/sh ### BEGIN INIT INFO # Provides: olad # Required-Start: $remote_fs $syslog # Required-Stop: $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: olad initscript # Description: Open Lighting Architecture init script ### END INIT INFO # Author: Lionel / domolio.fr # # Do NOT "set -e" # PATH should only include /usr/* if it runs after the mountnfs.sh script PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin/ DESC="Open Lighting Architecture Daemon" NAME=olad DAEMON=/usr/local/bin/$NAME DAEMON_ARGS="-f" PIDFILE=/var/run/$NAME.pid SCRIPTNAME=/etc/init.d/$NAME USER=olad # Exit if the package is not installed [ -x "$DAEMON" ] || exit 0 # Read configuration variable file if it is present [ -r /etc/default/$NAME ] && . /etc/default/$NAME # Load the VERBOSE setting and other rcS variables . /lib/init/vars.sh # Define LSB log_* functions. # Depend on lsb-base (>= 3.2-14) to ensure that this file is present # and status_of_proc is working. . /lib/lsb/init-functions # # Function that starts the daemon/service # do_start() { # Return # 0 if daemon has been started # 1 if daemon was already running # 2 if daemon could not be started start-stop-daemon --start --chuid $USER --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ || return 1 start-stop-daemon --start --chuid $USER --quiet --pidfile $PIDFILE --exec $DAEMON -- \ $DAEMON_ARGS \ || return 2 # Add code here, if necessary, that waits for the process to be ready # to handle requests from services started subsequently which depend # on this one. As a last resort, sleep for some time. } # # Function that stops the daemon/service # do_stop() { # Return # 0 if daemon has been stopped # 1 if daemon was already stopped # 2 if daemon could not be stopped # other if a failure occurred start-stop-daemon --chuid $USER --stop --quiet --retry=TERM/10/KILL/5 --pidfile $PIDFILE --name $NAME RETVAL="$?" [ "$RETVAL" = 2 ] && return 2 # Wait for children to finish too if this is a daemon that forks # and if the daemon is only ever run from this initscript. # If the above conditions are not satisfied then add some other code # that waits for the process to drop all resources that could be # needed by services started subsequently. A last resort is to # sleep for some time. start-stop-daemon --chuid $USER --stop --quiet --oknodo --retry=0/10/KILL/5 --exec $DAEMON [ "$?" = 2 ] && return 2 # Many daemons don't delete their pidfiles when they exit. rm -f $PIDFILE return "$RETVAL" } # # Function that sends a SIGHUP to the daemon/service # do_reload() { # # If the daemon can reload its configuration without # restarting (for example, when it is sent a SIGHUP), # then implement that here. # start-stop-daemon --chuid $USER --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME return 0 } case "$1" in start) [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" do_start case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; stop) [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" do_stop case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; status) status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? ;; #reload|force-reload) # # If do_reload() is not implemented then leave this commented out # and leave 'force-reload' as an alias for 'restart'. # #log_daemon_msg "Reloading $DESC" "$NAME" #do_reload #log_end_msg $? #;; restart|force-reload) # # If the "reload" option is implemented then remove the # 'force-reload' alias # log_daemon_msg "Restarting $DESC" "$NAME" do_stop case "$?" in 0|1) do_start case "$?" in 0) log_end_msg 0 ;; 1) log_end_msg 1 ;; # Old process is still running *) log_end_msg 1 ;; # Failed to start esac ;; *) # Failed to stop log_end_msg 1 ;; esac ;; *) #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 exit 3 ;; esac :
# insserv olad
Ainsi Open Lighting Architecture sera lancé à chaque démarrage.
Conclusion d’OLA
Cool, ça prend forme !
Il manque un gros détail : ça ne me semble pas viable de contrôler les couleurs à travers l’interface d’administration d’OLA, j’ai besoin de le faire en dehors de cette interface.
D’où l’intérêt d’avoir installé les libraires python d’Open Lighting Architecture.
Prise de contrôle du DMX par PHP & Python
Mon idée est de pouvoir disposer d’une roue chromatique depuis mon smartphone et pouvoir changer les couleurs de mon escalier, par exemple, pour la frime par challenge.
OLA a été installé avec des librairies python, servons-nous en.
Le wiki d’OLA fait part de scripts simple en pyhton.
import array from ola.ClientWrapper import ClientWrapper def DmxSent(state): wrapper.Stop() universe = 1 data = array.array('B') data.append(10) data.append(50) data.append(255) wrapper = ClientWrapper() client = wrapper.Client() client.SendDmx(universe, data, DmxSent) wrapper.Run()
En ayant olad d’exécuté, on lance :
Si le script ne vous retourne rien et vous éclaire d’un joli bleu, tant mieux !
A l’inverse, si le script python retourne ceci :
Traceback (most recent call last): File "testor.py", line 2, infrom ola.ClientWrapper import ClientWrapper ImportError: No module named ola.ClientWrapper
C’est que libraires d’OLA ne sont pas importables par le script.
L’une des solutions consiste à copier les libraires dans le script de travail de python.
Relancez le script et la vie est bleue !
Voila pour la partie pyhton -> DMX.
Place maintenant à la partie PHP Python.
Dans mon délire d’avoir une roue chromatique ou quelque chose qui y ressemble, j’ai utilisé un script jQuery.
J’ai modifié le script pyhton pour lui passer les couleurs RGB en argument :
#!/usr/bin/python import sys, array from ola.ClientWrapper import ClientWrapper def DmxSent(state): wrapper.Stop() universe = 1 data = array.array('B') data.append(int(sys.argv[1])) data.append(int(sys.argv[2])) data.append(int(sys.argv[3])) wrapper = ClientWrapper() client = wrapper.Client() client.SendDmx(universe, data, DmxSent) wrapper.Run()
Script nommé dmxchangecolor.py placé dans /usr/local/bin/ avec droit en éxecution.
Ma page php :
<?php function html2rgb($color) { if ($color[0] == '#') $color = substr($color, 1); if (strlen($color) == 6) list($r, $g, $b) = array($color[0].$color[1], $color[2].$color[3], $color[4].$color[5]); elseif (strlen($color) == 3) list($r, $g, $b) = array($color[0].$color[0], $color[1].$color[1], $color[2].$color[2]); else return false; $r = hexdec($r); $g = hexdec($g); $b = hexdec($b); return array($r, $g, $b); } if(isset($_POST['envoyer'])) { $htmlcolor = $_POST['htmlcolor']; $rgb = html2rgb($htmlcolor); exec("dmxchangecolor.py $rgb[0] $rgb[1] $rgb[2]"); } ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <link rel="stylesheet" href="css/colorpicker.css" type="text/css" /> <link rel="stylesheet" media="screen" type="text/css" href="css/layout.css" /> <title>ColorPicker DMX - jQuery plugin</title> <script type="text/javascript" src="js/jquery.js"></script> <script type="text/javascript" src="js/colorpicker.js"></script> <script type="text/javascript" src="js/eye.js"></script> <script type="text/javascript" src="js/utils.js"></script> <script type="text/javascript" src="js/layout.js?ver=1.0.2"></script> </head> <body> <form action="" method="post" > <input type="text" maxlength="6" size="6" id="colorpickerField" name="htmlcolor" value="<?php echo $htmlcolor ?>" /><br /> <input type="submit" value="Envoyer" name="envoyer" /> </form> </body> </html>
Simple et efficace, je choisis ma couleur, valide le formulaire, la fonction php convertit la couleur HTML en RGB qui la soumet au script python.
Télécharger le script PHP/jQuery/DMX.
Et voila ! J’aurais préféré une libraire PHP comme pour le KNX et le 1-wire, mais je n’en ai pas trouvé malheureusement.
Passer les couleurs à un script python lui même appelé commande php, ça fera l’affaire pour ma future machine à gaz.
Php Show Controller
Autrement, j’ai découvert Php Show Controller (PSC) et son sympathique créateur Laurent.
https://github.com/RenZ0/php-show-controller
L’outil est très intéressant si l’on souhaite scénariser ses lumières.
Le principe est résumé dans le README :
With php interface you change sql data. Python engine reads sql data, and send it to OLA. OLA use your hardware to send DMX signal.
Le paquet a un petit défaut, il n’est pas encore compatible PHP 5.4 car il exige les register_globals (qui ont disparus depuis php 5.4)
Mais d’après l’auteur, les modifications sont pour très bientôt, pour les impatients, en modifiant les quelques GET et POST vous pourrez vite vous faire une idée de la puissance de PSC.
Voici comment l’installer :
Il faut dans un premier temps python-mysqldb et bien sûr un serveur MySQL.
# wget "http://www.imaginux.com/ccount/click.php?id=148" -O php-show-controller_1.1.1.tar.gz
# tar zxvf php-show-controller_1.1.1.tar.gz
# cd php-show-controller
On crée une base nommée psc.
mysql -p create database psc; quit;
On importe la structure et les paramètres de base :
# mysql -p psc < sql/psc_base.sql
Puis on copie les fichiers web pour apache2
# cd /var/www/
# chown -R www-data:www-data psc
On modifie le fichier des paramètres MySQL pour que PHP s’en serve :
Et les paramètres MySQL pour que pyton utilise également MySQL :
# vi config.py
Et rendez-vous sur la page web de votre installation pour découvrir PSC.
N’oubliez pas de vous aider de README pour le paramétrage de votre scénario.
Conclusion
Après avoir installé les drivers Open DMX USB, après avoir installé Open Lighting Architecture et ses libraires, après avoir testé un scripts python et php.
Je suis sûr et certain de pouvoir introduire des lumières d’ambiances dans mon projet domotique.
Escalier, pièces à vivre, informations lumineuses, les idées sont nombreuses, mais je sais que je pourrais le faire et introduire les interactions dans mon usine à gaz par l’intermédiaire de pages php.
Prise en main du KNX en php
Ou comment allumer une lampe depuis une page web.
Après avoir testé linknx et knxweb, via le LiveCD de Ziki, je n’ai pas trop adhéré au principe du duo linknx + knxweb.
C’est sympa, mais pas assez souple par rapport à ce que je souhaite faire avec le 1-wire et le DMX, j’ai donc le choix entre monter une usine à gaz à partir de knxweb ou partir de zéro.
J’ai choisi de partir de zéro.
J’ai furtivement étudié le fonctionnement de linknx via la page d’example php, je prends note de ce fonctionnement xml.
En tirant un trait à linknx, je perdrai la notion de « rules », cette façon de gérer très rapidement des conditions.
A ce moment précis, je ne sais pas comment je vais faire sans.
En compilant eibnetmux, j’ai constaté qu’il embarqué un package php, en cherchant un peu, j’ai trouvé un paquet de samples :
# wget "http://downloads.sourceforge.net/project/eibnetmux/Sample%20client%20applications/1.7.0/eibnetmuxclientsamples-1.7.0.tar.gz?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Feibnetmux%2Ffiles%2FSample%2520client%2520applications%2F1.7.0%2F&ts=1334692571&use_mirror=freefr" -O eibnetmuxclientsamples-1.7.0.tar.gz
# tar zxvf eibnetmuxclientsamples-1.7.0.tar.gz
Je ne prends que la partie php, donc je ne compile rien.
# cp *.php /var/www/
Et le fichier php comprenant les class provient de la compilation.
Hum hum ! writegroup.php et readgroup.php on l’air sympa, voyons voir ça …
Usage: writegroup.php "hostname" "group" "eis" "value" [group eis value ...]
la lampe s’allume !
la lampe s’éteint.
0/0/11 étant un groupe d’adresse correspondant à on/off d’une lampe relié sur un TXA213.
eis étant le type de format, dont les valeurs possibles sont très bien reprises dans une doc d’ABB (page 15).
J’ai repris ces 2 fichiers php pour réaliser une page de test avec des fonctions php.
<?php include( "eibnetmux.php" ); $hostname = "127.0.0.1"; function readgroup($groupaddress,$eis) { global $hostname; $c = new eibnetmux( "php_client", $hostname, 4390 ); $groupaddress = new KNXgroup( $groupaddress, $eis ); $value = $groupaddress->read( $c ); return $value; $c->close(); } function readgroupboolean($groupaddress) { global $hostname; $c = new eibnetmux( "php_client", $hostname, 4390 ); $groupaddress = new KNXgroup( $groupaddress,1); $value = $groupaddress->read( $c ); //return $value; if ($value == 1) return true; elseif ($value == 0) return false; $c->close(); } function writegroup($groupaddress,$eis,$value) { global $hostname; $c = new eibnetmux( "php_client", $hostname, 4390 ); $groupaddress = new KNXgroup( $groupaddress, $eis ); $r = $groupaddress->write( $c, $value); return true; //$value = $groupaddress->read( $c ); //print "New value of $argv[$idx]: $value\n"; $c->close(); } if($_POST['action'] == 'on') writegroup($_POST['groupaddress'],1,1); if($_POST['action'] == 'off') writegroup($_POST['groupaddress'],1,0); ?> La lampe est <?php echo (readgroupboolean('0/0/11'))?"allumée":"eteinte"; ?><br /> <br /> <form action="#" method="POST"> <input type="hidden" readonly="readonly" name="action" value="<?php echo (readgroupboolean('0/0/11'))?"off":"on"; ?>" /> <input type="hidden" readonly="readonly" name="groupaddress" value="0/0/11" /> <input type="submit" name="toggle" value="<?php echo (readgroupboolean('0/0/11'))?"Eteindre":"Allumer"; ?> la lampe" /> </form>
Je voyais cette montagne plus impressionnante qu’elle l’est réellement, maintenant que j’ai franchi ce cap, je suis davantage d’attaque pour des fonctions avancées du KNX à travers PHP.
Affaire à suivre !