La solution de repli un script perl

Suite à mon article sur l’exploitation des informations de l’appareil BaroWatt « baromètre énergétique » de la société Watteco et à ma tentative infructueuse pour faire fonctionner le logiciel CurrentCost GUI écrit en python, j’ai finalement opté pour la réalisation d’un script perl.

Mon but étant de pouvoir ajouter un programme lancé au démarrage de mon serveur domotique cette solution me semble être la plus pertinante.

Je vais donc ici noté pas à pas mes investigations pour réaliser cette mission.

Objectif : Lire et sauvegarder dans une base de données les informations envoyés par le Baromètre énergétique.

Prérequis : avoir installer le driver pour le cable USB téléchargeable ici

Perl et SqLite3 sont tous deux présent de base sur un système mac OSX 10.6.

SQLite est présent dans le dossier /usr/bin/sqlite3 et il suffit de pater la commande  ci-dessous dans une fenêtre de terminal pour s’en assurer.

[code type= »shell »]perl –version[/code]

En suite il faut ajouter un module perl indispensable pour accéder aux port séries (port com) mais ATTENTION il est impératif que le logiciel Xcode soit installé sur votre ordinateur ou un compilateur C.
Pour ajouter ce module il suffit depuis une fenêtre de terminal (Application->Utilitaires->Terminal) de taper les commandes :

[code type= »shell »]
perl -MCPAN -e shell
Device::SerialPort
[/code]

Utilisation de SQLite3 ou MySql

Mac OS X 10.6 fournit nativement le support de SQLite3 il est donc simple et facile de sauvegardé les données remontées par l’appareil BaroWatt ou CiurrentCost dans une DB de ce type.

Il faut ajouter les modules permettant d’accéder à SQLite depuis perl (modules DBI et DBD pour SQLite).
Donc toujours depuis le terminal

[code type= »shell »]
perl -MCPAN -e shell # (si vous n’êtes plus dans la ligne de commande perl)
install DBI
install DBD::Mysql
install DBD::SQLite
[/code]

Impossible d’installer le driver MySql avec la version de perl installé sur Mac OSX 10.6.

Le but étant d’obtenir une procédure simple permettant à un non informaticien de procéder à l’installation du script la piste de résolution de ce problème sera laissée de côté au profits de l’utilisation de SQLite.

Script utilisant SQLite3 pour enregistrer les données XML remontées par le baromètre énergétique

[code type= »perl »]
#!/usr/bin/perl -w

# Lecture d’information envoyé par l’appareil "Current Cost" ou "BaroWatt" via cable USB et émulation de port série et enregistrement dans Database SQLITE;
use DBI;
use strict;
use Device::SerialPort qw( :PARAM :STAT 0.07 );

my $PORT = "/dev/tty.usbserial";

my $dbfile = ‘BaroWattV3.sqlite’; # your database file
#my $dbfile = ‘/WWW/SQLIteBaroWatt.db’; # your database file
my $dbh = DBI->connect( # connect to your database, create if needed
"dbi:SQLite:dbname=$dbfile", # DSN: dbi, driver, database file
"", # no user
"", # no password
{ RaiseError => 1 }, # complain if something goes wrong
) or die $DBI::errstr;

$dbh->do("CREATE TABLE IF NOT EXISTS xmldata (time DATE, data TEXT)") or die $DBI::errstr;
$dbh->do("CREATE TABLE IF NOT EXISTS xmldataRT (time DATE, temp TEXT, watts TEXT)") or die $DBI::errstr;
$dbh->do("CREATE TABLE IF NOT EXISTS xmldataHH (time DATE, unit TEXT, val TEXT)") or die $DBI::errstr;
$dbh->do("CREATE TABLE IF NOT EXISTS xmldataHD (time DATE, unit TEXT, val TEXT)") or die $DBI::errstr;
$dbh->do("CREATE TABLE IF NOT EXISTS xmldataHM (time DATE, unit TEXT, val TEXT)") or die $DBI::errstr;

my $ob = Device::SerialPort->new($PORT);
$ob->baudrate(57600);
$ob->write_settings;

# déclaration et initialisation des valeurs de références pour savoir si on doit traiter les infos remontées et faire un insert en DB
my $dateAnter = time – (365 * 24 * 60 * 60) – 36000; # un an et 10 heures au paravant
my ($lastHM, $lastHD, $lastHH) = (localtime($dateAnter))[4,3,2];
#print "Val de ref : lastHM = ".$lastHM." lastHD = ".$lastHD." lastHH = ".$lastHH.".\n";
#my ($month, $day, $hour) = (localtime(time))[4,3,2];

open(SERIAL, "+>$PORT");
while (my $line = <SERIAL>) {

# [4,3,2] récupération du mois, jour et heure depuis le tableau retourné par localtime
my ($month, $day, $hour) = (localtime(time))[4,3,2];
#print "Val de ref : lastHM = ".$lastHM." – ".$month." lastHD = ".$lastHD." – ".$day." lastHH = ".$lastHH." – ".$hour.".\n";

if ($line =~ m!<tmpr> *([\-\d.]+)</tmpr>.*<ch1><watts>0*(\d+)</watts></ch1>!) {
print "Temps réel \n";
#Information temps réel (toutes les 6 secondes avec mon BaroWatt) #print "insert into xmldataRT (time, temp, watts) values (datetime(‘now’ , ‘localtime’),’".$1."’,’".$2."’)";
$dbh->do("insert into xmldataRT (time, temp, watts) values (datetime(‘now’ , ‘localtime’),’".$1."’,’".$2."’)") or die $DBI::errstr;
} elsif ($line =~ m!<units> *(\w+)</units>.*<h004>(\d+)</h004>!) {
print "H-4 \n";
#Information historique h-4 (toutes les 2 heures avec mon BaroWatt) je sais pas pourquoi il y a pas de h002 envoyé par mon cc128
if (!($hour == $lastHH)) {
$dbh->do("insert into xmldataHH (time, unit, val) values (datetime(‘now’ , ‘localtime’),’".$1."’,’".$2."’)") or die $DBI::errstr;
$lastHH = $hour;
}
} elsif ($line =~ m!<units> *(\w+)</units>.*<d001>(\d+)</d001>!) {
print "D-1 \n";
#Information historique j-1 (toutes les 2 heures avec mon BaroWatt)
if (!($day == $lastHD)) {
$dbh->do("insert into xmldataHD (time, unit, val) values (datetime(‘now’ , ‘localtime’),’".$1."’,’".$2."’)") or die $DBI::errstr;
$lastHD = $day;
}
} elsif ($line =~ m!<units> *(\w+)</units>.*<m001>(\d+)</m001>!) {
print "M-1 \n";
#Information historique m-1 (toutes les 2 heures avec mon BaroWatt)
if (!($month == $lastHM)) {
$dbh->do("insert into xmldataHM (time, unit, val) values (datetime(‘now’ , ‘localtime’),’".$1."’,’".$2."’)") or die $DBI::errstr;
$lastHM = $month;
}
}
# LES AUTRES MESSAGES XML NE SONT PAS UTILES SI TOUTE FOIS ON SOUHAITE LES ENREGISTRER ALORS IL SUFFIT DE SUPPRIMER LES # EN DEBUT DES 5 LIGNES CI-DESSOUS
# elsif (!($line eq  ») && (length($line)>21)) {
# print "Stange \n";
# $dbh->do("insert into xmldata (time, data) values (datetime(‘now’ , ‘localtime’),’".$line."’)") or die $DBI::errstr;
# #$dbh->do("select ROWID,* from xmldata;"); requète permettant de récuperer tous les champs plus le champs autoincrement automatiquement àjouté à la table par SQLITE
#}
print $line."\n";
}

$dbh->disconnect();
[/code]

Hélas lorsque l’on décide d’accéder à cette database SQLite3 depuis php pour exploiter les informations et réaliser les graphiques, bien que la version de PHP soit la version 5.3 et que le support SQLite soit activé, il ne parvient pas à lire des fichiers SQLite3 mais uniquement des fichiers SQLite2.

La encore pur des raisons de simplicité de mise en oeuvre par les éventuels utilisateur de ce script nous ne suivrons pas la piste de résolution décrite ici.

Finalement on utilisera MySql son installation devrait être « aisée » (MySql n’est pas installé d’origine sur les version non Server de Snow Léopard) et la quantité d’information enregistré s’avérant importante (un message toutes les 6 secondes avec mon BaroWatt) l’exploitation des informations enregistrées dans la data-base devrait être plus rapide qu’avec SQLite.

Si vous avez lu cet article en détail vous savez déjà que l’installation du driver DBD pour MySql à échoué, aussi nous allons devoir ruser.
Les requêtes seront réalisées par le biais de l’appel de la fonction perl « system(‘echo  ».$sqlins. » | mysql -u SQLUTILISATEUR –password=VOTREMDP conso_watt_et_temp’); » en remplaçant SQLUTILISATEUR et VOTREMDP par les infos qui vont bien 😉

Pour commencer voici la structure de la DB nommée « conso_watt_et_temp » (requète à réaliser depuis PhpMyAdmin ou MysQlWorkBench)

[code type= »sql »]
SET SQL_MODE=&quot;NO_AUTO_VALUE_ON_ZERO&quot;
— Base de données: `conso_watt_et_temp`
— ——————————————————–

— Structure de la table `test`

CREATE TABLE IF NOT EXISTS `test` (`id` int(11) NOT NULL auto_increment,`val` text NOT NULL,PRIMARY KEY (`id`)) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
— ——————————————————–

— Structure de la table `xmldata`

CREATE TABLE IF NOT EXISTS `xmldata` (`date` datetime NOT NULL default ‘0000-00-00 00:00:00’,`data` text,PRIMARY KEY (`date`)) ENGINE=MyISAM DEFAULT CHARSET=utf8;
— ——————————————————–

— Structure de la table `xmldataHH`

CREATE TABLE IF NOT EXISTS `xmldataHH` (`date` datetime NOT NULL default ‘0000-00-00 00:00:00’,`unit` char(64) default NULL,`val` float default NULL,PRIMARY KEY (`date`)) ENGINE=MyISAM DEFAULT CHARSET=utf8;
— ——————————————————–

— Structure de la table `xmldataHJ`

CREATE TABLE IF NOT EXISTS `xmldataHJ` (`date` datetime NOT NULL default ‘0000-00-00 00:00:00’,`unit` char(64) default NULL,`val` float default NULL,PRIMARY KEY (`date`)) ENGINE=MyISAM DEFAULT CHARSET=utf8;
— ——————————————————–

— Structure de la table `xmldataHM`

CREATE TABLE IF NOT EXISTS `xmldataHM` (`date` datetime NOT NULL default ‘0000-00-00 00:00:00’,`unit` char(64) default NULL,`val` float default NULL,PRIMARY KEY (`date`)) ENGINE=MyISAM DEFAULT CHARSET=utf8;
— ——————————————————–

— Structure de la table `xmldataRT`

CREATE TABLE IF NOT EXISTS `xmldataRT` (`date` datetime NOT NULL default ‘0000-00-00 00:00:00’,`temp` float default NULL,`watts` float default NULL,PRIMARY KEY (`date`)) ENGINE=MyISAM DEFAULT CHARSET=utf8;
[/code]

Script final ci-dessous utilisant MySql via la fonction perl « system() »

[code type= »perl »]
#!/usr/bin/perl -w

# Lecture d’information envoyé par l’appareil "Current Cost" ou "BaroWatt" via cable USB et émulation de port série;
use strict;
use Device::SerialPort qw( :PARAM :STAT 0.07 );
use Date::Calc qw(Add_Delta_YMDHMS Today_and_Now);

my $PORT = "/dev/tty.usbserial";

my $ob = Device::SerialPort->new($PORT);
$ob->baudrate(57600);
$ob->write_settings;

# déclaration et initialisation des valeurs de références pour savoir si on doit traiter les infos remontées et faire un insert en DB
my ($year,$lastHM,$lastHD, $lastHH,$min,$sec) = Add_Delta_YMDHMS(Today_and_Now(), 0,0,0,-3,0,0); # 3 heures auparavant car il faut avoir lancer le script de récup d’historique au préalable si on veut le conserver

open(SERIAL, "+>$PORT");
while (my $line = <SERIAL>) {
my $sqlins = "";

# [4,3,2] récupération du mois, jour et heure depuis le tableau retourné par localtime
my ($refMonth, $refDay, $refHour) = (localtime(time))[4,3,2];
#print "Val de ref : lastHM = ".$lastHM." – ".$month." lastHD = ".$lastHD." – ".$day." lastHH = ".$lastHH." – ".$hour.".\n";

if ($line =~ m!<tmpr> *([\-\d.]+)</tmpr>.*<ch1><watts>0*(\d+)</watts></ch1>!) {
print "Temps réel \n";
#Information temps réel (toutes les 6 secondes avec mon BaroWatt)
$sqlins = ‘insert into xmldataRT (date, temp, watts) values (now(),’.$1.’,’.$2.’)’;
} elsif ($line =~ m!<units> *(\w+)</units>.*<h004>(\d+)</h004>!) {
print "H-4 \n";
#Information historique h-4 (toutes les 2 heures avec mon BaroWatt) je sais pas pourquoi il y a pas de h002 envoyé par mon cc128
if (!($refHour == $lastHH)) {
$lastHH = $refHour;
my ($year,$month,$day, $hour,$min,$sec) = Add_Delta_YMDHMS(Today_and_Now(), 0,0,0,-4,0,0);# on retranche les 4 heures à la date actuelle
my $strHeureMesure = sprintf("%4d-%02d-%02d %02d:%02d:%02d",$year,$month,$day,$hour,$min,$sec);
$sqlins = ‘insert into xmldataHH (date, unit, val) values ("’.$strHeureMesure.’","’.$1.’",’.$2.’)’;
}
} elsif ($line =~ m!<units> *(\w+)</units>.*<d001>(\d+)</d001>!) {
print "D-1 \n";
#Information historique j-1 (toutes les 2 heures avec mon BaroWatt)
if (!($refDay == $lastHD)) {
$lastHD = $refDay;
my ($year,$month,$day, $hour,$min,$sec) = Add_Delta_YMDHMS(Today_and_Now(), 0,0,-1,0,0,0);# on retranche 1 jour à la date actuelle
my $strHeureMesure = sprintf("%4d-%02d-%02d %02d:%02d:%02d",$year,$month,$day,$hour,$min,$sec);
$sqlins = ‘insert into xmldataHD (date, unit, val) values ("’.$strHeureMesure.’","’.$1.’",’.$2.’)’;
}
} elsif ($line =~ m!<units> *(\w+)</units>.*<m001>(\d+)</m001>!) {
print "M-1 \n";
#Information historique m-1 (toutes les 2 heures avec mon BaroWatt)
if (!($refMonth == $lastHM)) {
$lastHM = $refMonth;
my ($year,$month,$day, $hour,$min,$sec) = Add_Delta_YMDHMS(Today_and_Now(), 0,-1,0,0,0,0);# on retranche 1 mois à la date actuelle
my $strHeureMesure = sprintf("%4d-%02d-%02d %02d:%02d:%02d",$year,$month,$day,$hour,$min,$sec);
$sqlins = ‘insert into xmldataHM (date, unit, val) values ("’.$strHeureMesure.’","’.$1.’",’.$2.’)’;
}
}

if (!($sqlins eq  »)) {
system(‘echo \ ».$sqlins.’\’ | mysql -u root –password=asnow06 conso_watt_et_temp’);
}

print $sqlins."\n".$line."\n";
}
[/code]

Démarrage automatique du script à l’allumage

Il suffit d’ajouter le fichier com.eticweb.barowatt.currentcost.plist qui sera utilisé par launchctl dans le dossier /Library/LaunchDaemons/ (Library = Bibliothèque depuis le finder).

Le contenu du fichier :

[code type= »xml »]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>KeepAlive</key>
<true/>
<key>Label</key>
<string>com.eticweb.barowatt.currentcost</string>
<key>OnDemand</key>
<false/>
<key>RunAtLoad</key>
<true />
<key>ProgramArguments</key>
<array>
<string>/Groups/EticWeb/SnowLeoServ/ReadSerialPort-barowatt-PerlOK-Mysql.sh</string>
</array>
<key>StandardErrorPath</key>
<string>/dev/null</string>
<key>StandardOutPath</key>
<string>/dev/null</string>
</dict>
</plist>
[/code]

Next step

Super on à les informations enregistrées dans une base de données mais on fait comment pour les afficher graphiquement?

Et bien on réalise une application en PHP par exemple qui utilise du javascript (jqPlot) pour faire de beaux graphique.

Un premier script PHP utilisant jqPlot et le script perl dont je viens de parler sont téléchargeable via le projet FolloWatt que j’ai déposé sur GitHub