Archives par étiquette : serveur

Migration de serveur

Bonjour à toutes à et à tous,

Je viens de changer de serveur d’hébergement pour MagdiBlog.fr, aussi, veuillez m’excuser pour les coupures qui ont pu avoir lieux ces dernières 24h. Il se peut également que certains commentaires aient été perdus lors de la migration, mais c’est pour la bonne cause, le site devrait être beaucoup réactif à présent 🙂

Je profite de ce billet pour vous annoncer que le projet Pi BOAt n’est pas mort, les articles paraitront à partir de la rentrée 🙂

En outre, je prépare un article sur la partie vidéo, l’utilisation d’objectifs sur la Pi Camera ainsi que sur les différentes manières d’utiliser le module caméra sur le Pi 🙂

Profitez bien des quelques jours d’août qu’il nous reste,

A très bientôt,

Olivier

9 – Module 6 – Graphique de la bande passante

Pour diverses raisons, il peut intéressant de s’avoir si notre bande passante internet est saturée ou non. Ce module affiche un historique d’environ 5 minutes de l’utilisation de la bande passante descendante (download) et montante (upload). De cette manière, je peux voir en un seul coup d’oeil si quelque chose d’anormal se passe sur mon réseau.

jarvis_screenshot_ifstat

Introduction

Ce module est de loin le plus compliqué de tous. D’une part, il va falloir récupérer le débit instantané utilisé par l’interface réseau de la gateway qui est reliée à la Box internet. Puis, il va falloir transmettre ces données au Pi sur lequel fonctionne le dashscreen. Enfin, il faudra créer un graphique pour représenter ces informations…

En effet, j’ai choisi de ne pas utiliser de soft type mrtg qui sont spécialisés pour ce genre de chose. Je préfère installer le minimum de soft, et utiliser quelques commandes de base. Pour faire quelque chose de simple (comme afficher la bande passante sur la graphique), pas besoin de sortir l’artillerie lourde 🙂 Et puis …si on ne se compliquait pas la vie, ce serait beaucoup moins drôle, n’est ce pas ? 😉

Mesurer la bande passante

ifstat est un petit outil en ligne de commande qui affiche les statistiques d’utilisation d’une interface réseau. Dans un terminal, taper la commande suivante :

ifstat -n -i eth0

Et vous obtiendrait en continue, quelque chose dans le genre :

       eth0       
 KB/s in  KB/s out
    0.45      0.06
    0.71      0.39
    2.85      0.76
    0.57      0.06
    1.86      0.19
    0.45      0.06
    0.45      0.06
    0.45      0.06
    0.57      0.06
    0.59      0.13
    0.45      0.06
    0.45      0.06
    0.59      0.26
    0.57      0.06
    1.74      0.13
    ...

Chaque seconde on obtient le débit instantané (ou presque), en KBytes/s, c’est à dire en Ko/s 🙂 C’est exactement ce qu’il nous faut 🙂 Il convient maintenant d’envoyer ces informations en continue sur notre Pi.

Transmettre les données au Pi à travers le réseau

Le moyen le plus simple de transmettre une info d’un ordinateur à un autre c’est de les envoyer dans des paquets UDP… C’est tellement trivial qu’il ma fallu plusieurs jours avant d’en avoir l’idée !

La commande netcat est tout à fait appropriée pour ce type d’opération. Sur une machine A qui joue le rôle de serveur faites :

nc -u -l 12345
  • -u : pour utiliser l’UDP
  • -l : pour passer en mode écoute (serveur)
  • 12345 : un numéro de port découte

Puis sur une machine B qui joue le rôle de client, faites :

nc -p 8888 -u 10.0.0.1 12345
  • -p 8888 : un port source
  • -u : pour utiliser l’UDP
  • 10.0.0.1 : adresse IP de la machine A (serveur)
  • 12345 : port d’écoute de la machine A (serveur)

Et hop, tout ce que vous tapez au clavier sur B, s’affiche sur A 🙂

Albert Einstein disait « Tout devrait être rendu aussi simple que possible, mais pas plus ! ».

A méditer 😉

Pour transmettre les données renvoyées par la commande ifstat à travers netcat, nous utiliserons un pipe. Voici les deux scripts complets (client/serveur) pour réaliser cette opération :

  • Script serveur à exécuter sur le Pi nc_ifstat_srv.sh :
### BEGIN INIT INFO 
# Provides: nc_ifstat_srv 
# Required-Start: 
# Required-Stop: 
# Default-Start: 2 3 4 5 
# Default-Stop: 0 1 6 
# Short-Description: ifstat for Jarvis 
# Description: Enable service provided by daemon. 
### END INIT INFO

dst_port=12345
dst_dir=/home/jarvis/ifstat
dst_file=eth0.log

nc -u -l $dst_port >> $dst_dir/$dst_file&
  • Script client à exécuter sur votre gateway/routeur nc_ifstat_clt.sh  :
### BEGIN INIT INFO
# Provides:          nc_ifstat_clt
# Required-Start:
# Required-Stop:
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: ifstat for Jarvis
# Description:       Enable service provided by daemon.
### END INIT INFO

src_port=8888
dst_port=12345
dst_host=10.0.0.2
eth=eth0

nc_ifstat()
{
	while [ "true" ]
	do
  		ifstat -n -i $eth | nc -p $src_port -u $dst_host $dst_port
  		sleep 5
	done
}

case "$1" in
        stop)
		echo " Stoping ifstat for Jarvis..."
                killall ifstat
		killall nc
		;;
        start)
		echo " Starting ifstat for Jarvis..."
		nc_ifstat&
		exit 0	
                ;;
        *)
                exit 1
                ;;
esac

exit 0

Pour exécuter le script au démarrage de la machine, placer le dans le répertoire /etc/init.d puis lancer la commande :

update-rc.d nom_du_script.sh defaults

A ce stade, vous devriez avoir un fichier sur votre Pi dans lequel, chaque seconde, la bande passante instantanée y est ajoutée en fin de fichier 🙂 Il ne nous reste plus qu’à lire ce fichier avec un script PHP, et dessiner le graphique pour représenter les données qu’il contient 🙂

Création du graphique en PHP

Ici encore, j’aurais pu utiliser une lib graphique en PHP, en JavaScript ou en n’importe quoi d’autre. Mais au lieux de sous exploiter une usine à gaz difficilement personnalisable, j’ai préféré créer ma propre fonction graphique en PHP. Vous allez voir que c’est beaucoup plus simple que ce qu’on peut s’imaginer 🙂

Voici ma fonction imagickHisto() :

function imagickHisto ($max, $eth = '', $up_down = 'down') {

  $datas = parseData ("eth0.log", $up_down);

  $width            = 304; // largeur du graphique
  $height           = 100; // hauteur du graphique
  $padding          = 1;
  $ticks            = 5;
  $background_color = '#000'; // couleur du fond
  $axes_color       = '#555'; // couleur des axes

  if($up_down == 'down'){
    $data_color       = '#1D1'; // couleur du graphique pour le download
  }
  else{
    $data_color       = '#D11'; // couleur du graphique pour l'upload
  }

  $nb_values        = $width - 2*$padding - 2;
  $max_value        = $height - 2*$padding - 4;

  $nb_datas         = sizeof($datas);
  $trim             = $nb_values - $nb_datas;

  if($trim < 0){$trim = 0;}

  $image = new Imagick();
  $image ->newImage( $width, $height, new ImagickPixel($background_color) );
  $draw  = new ImagickDraw();
  $draw->setStrokeColor( new ImagickPixel($axes_color) );

  $xx1    = $padding;
  $xy1    = $height - $padding - 1;
  $xx2    = $width - $padding - 1;
  $xy2    = $xy1;
  $yx1    = $xx1;
  $yy1    = $xy1;
  $yx2    = $yx1;
  $yy2    = $padding;
  $half_y = $height/2;
  $half_x = $width/2;

  $draw->line  ( $xx1, $xy1, $xx2, $xy2 );
  $draw->line  ( $yx1, $yy1, $yx2, $yy2 );

  $draw->line  ( $yx1, $yy2, $yx1+$ticks, $yy2 );
  $draw->line  ( $yx1, $half_y, $yx1+$ticks, $half_y );

  $draw->setStrokeColor( new ImagickPixel($data_color) );

  $first_x = $xx1 + 1 + $trim;
  $last_x  = $xx2 - 1;
  $first_y = $xy1 - 1;
  $last_y  = $yy2 + 1;

  for($i=0;$i<$nb_values;$i++){
    if(isset($datas[$i])){
      $value   = $datas[$i]*$max_value/$max;
      $value_y = $first_y - $value;
      $value_x = $first_x + $i;
      $draw->line  ( $value_x, $first_y, $value_x, $value_y );
    }
  }

  $image->drawImage( $draw );

  $text_draw = new ImagickDraw();
  $text_draw->setFillColor($axes_color);
  $text_draw->setFontSize( 12 );
  $image->annotateImage($text_draw, $half_x-20, $padding+10, 0, "$eth - $up_down");

  $image->setImageFormat( "png" );
  header( "Content-Type: image/png" );
  echo $image;
  exit;
}

Veuillez accepter mes excuses pour le manque cruel de commentaire dont fait l’objet cette fonction. Pour résumer, la fonction prend trois paramètres :

  • $max : la valeur max (en Ko) pour borner le graphique. Par exemple, mon débit réel maximum atteint 1800 Ko/s. Je choisie donc $max = 2000.
  • $eth : nom de l’interface (exemple eth0)
  • $up_down : « up » ou « down » pour spécifier la couleur du graphique ainsi que les données à afficher issue de ifstat

La fonction lit un tableau de donnée renvoyé par la fonction parseData(), puis dessine sur le graphique une barre de 1px de large par donnée. Avec une largeur de 300px, on peut donc afficher 5 minutes d’historique de données 🙂

Voici la fonction parseData() dont le rôle est de lire le fichier contenant les données issues de la commande ifstat :

  function parseData ($stat_file, $up_down) {
    $datas = array();
    if(filemtime($stat_file) < time()-10){return $datas;}
    $stats = fopen($stat_file, 'r');
    while (($line = fgets($stats)) !== false) {
      $explode_line = str_word_count($line, 1, "0123456789.");
      if($up_down == 'down') {
        $datas[]  = $explode_line[0];
      }
      else{
        $datas[]  = $explode_line[1];
      }
    }
    fclose($stats);
    $datas = array_slice($datas, -300);
    return $datas;
  }

Cette fonction prend deux paramètres :

  • $stat_file : fichier à parser
  • $up_down : « up » ou « down » pour distinguer les données d’upload et de download

Pour la suite, nous reprennons la même démarche que pour les autres modules 🙂

index.php

Il faut une div par graphique ; nous en ajoutons donc deux au fichier index.php, une pour l’upload et une pour le download.

 <div id="ifstat_eth0_up"><img id="img_eth0_up" src="pict/blank.png"></div>
 <div id="ifstat_eth0_down"><img id="img_eth0_down" src="pict/blank.png"></div>

Comme pour le module Xplanet, il convient d’initier la source des balises images <img> avec une image vide blank.png.

style.css

Il suffit ici de positionner nos deux <div> :

/* ifstat */

div#ifstat_oberon_up
{
  right             : 20px;
  bottom            : 20px;
  position          : absolute;
  overflow          : hidden;
  background-color  : rgba(0, 0, 0, 1);
}

div#ifstat_oberon_down
{
  right             : 340px;
  bottom            : 20px;
  position          : absolute;
  overflow          : hidden;
  background-color  : rgba(0, 0, 0, 1);
}

javascript.js

Une simple requête AJAX permet de récupérer les graphiques à afficher.

/* ifstat */

var ifstat_timeout;

function ifstat () {

  var now             = new Date().getTime();

  var url_down = "ajax.php?block=ifstat&eth=wan&up_down=down&max=2000&hour="+now;
  var img_eth0_down = $("<img />").attr("src", url_eth0_down);
  $("#img_eth0_down").attr("src", url_eth0_down);

  var url_up   = "ajax.php?block=ifstat&eth=wan&up_down=up&max=150&hour="+now;
  var img_eth0_up = $("<img />").attr("src", url_eth0_up);
  $("#img_eth0_up").attr("src", url_eth0_up);

  ifstat_timeout = setTimeout("ifstat()", 5000);
}

Même principe que pour le module Xplanet, il convient de précharger les images avant de les afficher. Je fixe le délais de rafraichissement à 5 secondes, ce qui est suffisant.

ajax.php

Le code à ajouter dans ce fichier reste élémentaire comme pour les autres modules. Il ne faut toutefois pas oublier de prendre en compte les paramètres de la fonction imagickHisto() :

  /////////////////////////////////////////////////
  //  IFSTAT
  /////////////////////////////////////////////////

  if($block == 'ifstat'){
    imagickHisto ($_REQUEST['max'], $_REQUEST['eth'], $_REQUEST['up_down']);
  }


inc.php

Dans ce fichier, il suffit de copier-coller les fonctions imagickHisto() et parseData() qui ont été décrites plus haut dans cet article.

Voilà le rendu final en gros plan, lorsque la bande passante est très sollicitée :

ifstat

7 – Module 4 – Ping de serveur

Si vous avez des serveurs, des NAS, ou n’importe quel autre type de machine dont vous souhaitez connaitre l’état (allumé/éteint, ou disponible/indisponible), ce type de module, encore une fois très simple, vous sera d’une grande utilité 🙂

jarvis_screenshot_pingL’idée est d’afficher en vert les serveurs qui répondent correctement aux solicitations du module, et en rouge ceux qui ne répondent pas. Pour accentuer le caractère alarmant d’une machine qui ne répond pas, nous ajouterons une animation pour optenir ce résultat :

ping

index.php

Comme pour les autres modules, une seule <div> suffit.

 <div id="ping"></div>

La balise reste vide, et sera « remplie » par la fonction JavaScript.

style.css

Pour ce module, la feuille de style est à peine plus longue :

/* ping */

div#ping
{
  left              : 0px;
  bottom            : 0px;
  position          : absolute;
  overflow          : hidden;
  background-color  : rgba(0, 0, 0, 1);
}

td.ping
{
  font-size         : 30px;
  text-align        : left;
  border-radius     : 10px;
  padding           : 5px 10px;
  font-weight       : bold;
}

td.ping_up
{
  color             : #00DD00;
}

td.ping_down
{
  background-image  : url('pict/warning.gif');
  color             : #DD0000;;
}

Les noms des machines sont affichés dans un tableau <table> dont chaque case <td> peut soit utiliser le style « ping_up« , si la machine répond, soit « ping_down«  si la machine ne répond pas. Dans ce dernier cas, nous utiliserons une image, un gif animé, pour faire « clignoter » la case en rouge, du mois, en donner l’impression 🙂

javascript.js

La fonction javascript de ce module ne fait qu’une requête AJAX, et affiche le résultat dans la div de la page index.php :

/* PING */

var ping_timeout;

function ping ()
{
  $.ajax({
    async : false,
    type: "GET",
    url: "./ajax.php",
    data: "block=ping",
    success: function(html){
      $("#ping").html(html);
    }
  });

  ping_timeout = setTimeout("ping()", 30000);
}

Il est important d’avoir une vision temps réel de l’état de ses machines, nous fixons donc le délai de rafraichissement à 30 secondes. Vous pouvez réduire ce délai selon vos besoins, mais veillez à ce qu’il reste supérieur au temps que met la fonction PHP pour tester toutes vos machines. Dans mon cas, je surveille six machines dont trois à travers internet. En moyenne, il faut 4 à 5 secondes pour tester toutes les machines. Toutefois, il n’est pas rare que cela prenne 20 secondes ! J’ai donc fixer à 30 secondes le temps à attendre entre chaque rafraichissement du module.

ajax.php

Il suffit de rajouter ces quelques lignes au fichier ajax.php pour que la bonne fonction soit appelée :

/////////////////////////////////////////////////
  //  PING
  /////////////////////////////////////////////////

  if($block == 'ping'){
    echo ping();
  }

inc.php

Dans ce fichier nous allons déclarer la fonction ping() qui est chargée de lancer les requêtes sur les différents serveurs, puis de construire le tableau HTML qui sera affiché.

En général, pour surveiller l’état d’un serveur, on utilise la commande ping qui permet simplement de savoir si la machine ciblée répond aux requêtes ICMP. Cela pose plusieurs problèmes. En effet, il est fort probable que le trafic ICMP soit bloqué par un firewall en amont du serveur visé ce qui faussera le résultat. De plus, si vous souhaiter surveiller un serveur web par exemple, il se peut très bien que la machine réponde bien aux pings, mais que le service web soit indisponible. Les pings n’ont donc que peu d’intéret…

L’idée, est plutôt de verier si les services qui tournent sur nos machines sont bien disponibles. Pour cela, nous allons simplement ouvrir une socket grâce à la fonction PHP fsockopen(). Si l’ouverture de la socket échoue c’est que le service n’est pas disponible 🙂

/////////////////////////////////////////////////
  //  PING
  /////////////////////////////////////////////////

  function ping () {
    $hosts    = array();
    // Définition des machines à tester
    $hosts_ip = array(
                    'Oberon'    => array('10.0.0.1',  '22'),    // SSH
                    'Triton'    => array('10.0.0.2',  '15915'), // TeamSpeak
                    'Ananke'    => array('10.0.0.3',  '2049'),  // NFS
                    'ds110j'    => array('10.0.0.4',  '2049'),  // NFS
                    'ds210j'    => array('10.0.0.5',  '2049'),  // NFS
                    'ds212j'    => array('10.0.0.6',  '2048')   // NFS
                );

    // pour chaque machine on test l'ouverture d'une socket sur le port spécifié
    // on stock le résultat 'up' ou 'down' dans la variable $hosts
    foreach($hosts_ip as $hostname => $host_data){
      $host_ip    = $host_data[0];
      $host_port  = $host_data[1];
      $socket     = 0;
      $socket     = @fsockopen($host_ip, $host_port, $errno, $errstr, 3);
      if($socket && !$errno){$hosts[$hostname] = 'up';}
      else{$hosts[$hostname] = 'down';}
    }

    // construction du tableau HTML
    // pour chaque machine testée, on affiche son nom et on fixe le style 'ping_up' ou 'ping_down' à la balise td
    $html  = '';
    $html .= '<table cellspacing="10px">';
    $c=0;
    foreach($hosts as $hostname => $host_status){
      if($c == 0){$html .= '<tr>';}
      $html .= '<td class="ping ping_'.$host_status.'">'.$hostname.'</td>';
      $c++;
      if($c == 2){$c = 0; $html .= '</tr>';}
    }
    if($c != 0){$html .= '</tr>';}
    $html .= '</table>';

    return $html;
  }

La variable $hosts_ip contient la liste des machines à tester et afficher. Le nom à affiché, l’adresse IP, et le port TCP du service à tester y sont stocké. Vous devez adapter les noms, adresses IP et numéro de port à vos besoins.

Comme vous pouvez le voir dans les commentaires du code, selon la machine à tester, j’ouvre une socket sur des services tels que SSH, TeamSpeak et NFS.  Cela fonctionne également très bien sur des serveurs FTP, HTTP, POP3, etc,… Tant que le service à tester écoute sur un port TCP, cela fonctionnera 🙂

Note : Il est possible d’optimiser cette fonction PHP en parallèlisant l’ouverture des sockets sur chaque machine. En effet, cette fonction teste les machines une par une ce qui peut être long… En « forkant » cette tâche grâce à la fonction PHP pcntl_fork(), il est possible de lancer toutes les requêtes d’un seul coup et ainsi diminuer le temps total que met la fonction pour renvoyer le tableau HTML à afficher. N’hésitez pas à laisser un commentaire si vous souhaitez plus de détails sur cette manière de procéder 🙂

Optimisation en utilisant le fork

Suite à la demande de lepopeye en commentaire de cet article, je vais expliquer comment paralléliser l’ouverture des sockets et ainsi gagner un temps précieux à chaque exécution de la fonction.

Le principe est simple : Pour chaque machine à tester, nous allons créer un processus fils grâce à la fonction PHP fork_pcntl() qui lancera la fonction fsockopen(). Comme les processus fils et le processus père ne peuvent pas partager de variables (à moins de faire appel à des techniques de mémoire partagée…), il convient de stocker les résultats dans une petite base de données SQlite 🙂 Le processus père attend que tous les processus fils soient terminés pour aller lire le contenu de la base de données SQlite (qui contient l’état de chaque machine). Le reste est inchangé, nous construisons le tableau HTML à afficher comme dans la fonction ping() initiale 🙂 A savoir que le fork n’est possible qu’en ligne de commande, il faudra donc adapter le fichier ajax.php de cette manière :

  /////////////////////////////////////////////////
  //  PING
  /////////////////////////////////////////////////

  if($block == 'ping'){
    echo shell_exec('php5 fork.php'); // fork.php étant le fichier dans lequel se trouve la fonction ping_fork();
  }

Voici la version « forkée » de la fonction ping(), que vous pouvez utiliser directement en la copiant dans un fichier fork.php :

function ping_fork() {

  $hosts_ip = array(
                    'Oberon'    => array('10.0.0.1',  '22'),    // SSH
                    'Triton'    => array('10.0.0.2',  '15915'), // TeamSpeak
                    'Ananke'    => array('10.0.0.3',  '2049'),  // NFS
                    'ds110j'    => array('10.0.0.4',  '2049'),  // NFS
                    'ds210j'    => array('10.0.0.5',  '2049'),  // NFS
                    'ds212j'    => array('10.0.0.6',  '2048')   // NFS
                );

  $pids  = array();

  // Connexion à la base de données sqlite et création de la table hosts_status si elle n'existe pas encore
  $db = new SQLite3('ifstat/hosts.sqlite');
  $db->exec('CREATE TABLE IF NOT EXISTS hosts_status (host_name VARCHAR(10), host_status VARCHAR(5));');

  // pour chaque machine, on créé un processus fils
  foreach($hosts_ip as $host_name => $host){
    $pids[$host_name] = pcntl_fork();
    if(!$pids[$host_name]) {
      $socket = @fsockopen($host[0], $host[1], $errno, $errstr, 3);
      if($socket && !$errno){$status = 'up';}else{$status = 'down';}
      // on attend que la table hosts_status ne soit plus verrouillée par un éventuel accès concurrent
      if($db->busyTimeout(5000)){
        $db->exec("INSERT INTO hosts_status VALUES ('$host_name', '$status');");
      }
      exit();
    }
  }

  // le processus père doit attendre que tous les processus fils soient terminés
  foreach($pids as $host_name => $pid){
    pcntl_waitpid($pid, $status, WUNTRACED);
  }

  $results = $db->query('select * from hosts_status;');

  $html  = '';
  $html .= '<table cellspacing="10px">';
  $c=0;
  while($host = $results->fetchArray(SQLITE3_ASSOC)){
    if($c == 0){$html .= '<tr>';}
    $html .= '<td class="ping ping_'.$host['host_status'].'">'.$host['host_name'].'</td>';
    $c++;
    if($c == 2){$c = 0; $html .= '</tr>';}
  }
  if($c != 0){$html .= '</tr>';}
  $html .= '</table>';

  $db->exec("DELETE FROM hosts_status;");

  return $html;
}

echo ping_fork();

Le timeout de la fonction fsockopen() est ici fixé à 3 secondes, ce qui veut dire que l’on doit attendre au moins 3 secondes avant de déclarer la machine cible comme « down » (non disponible). Dans la version non forkée de la fonction ping() si une machine ne répondait pas, il fallait attendre 3 secondes avant de tester la machine suivante, et ainsi de suite. Avec mes 6 machines, si aucune ne répond, il aurait fallu attendre 6×3 = 18 secondes avant d’avoir le résultat final. Avec la version forkée, les 6 requêtes sont envoyées en même temps. Quelque soit le nombre de machines à tester, nous aurons donc une réponse au bout de 3 secondes maximum 🙂

J’utilise à présent cette version forkée sur mon dashscreen 🙂 Merci à lepopeye de m’avoir forcé un peu la main 😉

10 – Le serveur de monitoring

Au départ je souhaitais pouvoir prendre la main à distance sur le Pi pour m’assurer que tout fonctionne bien durant la semaine. Cependant, les contraintes d’autonomie du Pi sur batterie me forçant à ne temporiser sont fonctionnement 2 minutes toutes les 10 minutes, la fenêtre de tir pour choper le Pi lorsqu’il est up est franchement serrée.

Sans compter sur l’autonomie, je ne pourrai pas surveiller le Pi à longueur de journée pour voir si tout fonctionne bien, que rien ne gêne les prises de vue, etc,… Il faut donc que le Pi se manifeste tout seul, régulièrement : c’est le principe du « battement de coeur ». Le Pi envoi de lui même des battements de coeur réguliers, et si les battements de coeur s’arrêtent, c’est que quelque chose à mal tourné.

L’idée est donc d’utiliser un serveur distant, qui accusera réception de ces battements de coeur, et m’enverra un mail pour me dire que tout va bien. Chaque battement de coeur sera accompagné d’une miniature de la dernière photo prise. Tout est enregistré dans une base de données, et j’ai prévu une page qui récapitule toutes les battements de coeur enregistrés avec leur photo miniature. Ce qui me permettra également d’avoir un aperçu des travaux en direct.

Inutile d’envoyer un battement de coeur à chaque prise de vue. Un mail toutes les 10 minutes, c’est trop. Deux mails par heure, c’est mieux 🙂

Serveur web – Apache

Très concrètement, le serveur de monitoring est un simple serveur web Apache, accompagné d’une base de données MySQL et de quelques scripts PHP.

Je ne détail pas ici l’installation et la configuration de ces composants. C’est un sujet bien à part qui mériterait un chapitre complet. Si vous voulez faire pareil, vous pouvez utiliser un hébergement mutualisé chez Free ou OVH (les ftp Free mis à disposition des Freenautes font également parfaitement l’affaire).

Base de données

Je n’ai besoin que d’une seule table pour enregistrer chaque battement de coeur ainsi que la photo associée. Voici le schéma de la table heartbeat que j’utilise :

CREATE TABLE `heartbeat` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `date` int(11) NOT NULL,
  `log` text COLLATE utf8_bin NOT NULL,
  `img` text COLLATE utf8_bin NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1 ;

Description des champs :

  • id : un simple indexe auto incrémenté
  • date : champ entier pour stocker la date du battement de coeur sous la forme d’un timestamp
  • log : champt text permettant d’enregistrer des infos diverses
  • img : champ text contenant le nom de la miniature de la photo associée au battement de coeur

Fonctions et fichier de configuration

J’ai rassembler les fonctions génériques que j’utilise dans le fichier func.php que vous pouvez télécharger ici : func.php.tar.gz

L’ensemble des paramètres de configuration des scripts PHP sont rassemblés dans le fichier conf.php que vous pouvez télécharger ici : conf.php.tar.gz

Script PHP heartbeat.php

C’est ce script qui va être appelé par la commande curl comme expliqué dans le chapitre précédent. Sont rôle est de réceptionner la photo miniature, d’enregistrer la date de réception, et d’envoyer un mail.

Je vais à l’essentiel, le code de ce script est donc très minimaliste :

<?php

header('Content-type: text/html; charset=utf-8');

// inclusion des fichiers requis
require_once('conf.php');
require_once('func.php');

// initialisation de la session
$session_id = sessionStart();
// connexion à la base de données
$db_connect = dbConnect();
// nettoie la variable $_REQUEST
$_REQUEST   = clearRequest ($_REQUEST);

// enregistre la tentative de connexion dans un fichier de log
logIt('access', 'heartbeat connection ...');

// vérifie que le token donné correspond à celui attendu ;)
if(!empty($_REQUEST['token']) && $_REQUEST['token'] == $conf['token']['heartbeat']) {

  // si le fichier est bien réceptionné
  if (is_uploaded_file($_FILES['img']['tmp_name'])) {
    if (move_uploaded_file($_FILES['img']['tmp_name'], $conf['upload_dir'].'/'.$_FILES['img']['name'])) {

      $field_data = array (
                              'date'  => time(),
                              'img'   => $_FILES['img']['name'],
                              'log'   => ''
                          );
      // enregistre le battement de coeur en base de données dans la table heartbeat
      $add        = addData ('heartbeat', $field_data);

      // titre du mail à envoyer
      $mail_title = 'Pi TimeLapse - HeartBeat '.date("d.m.y H:i:s");

      // corps du mail à envoyer, contenant la date et la minature
      $mail_core  = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
                      <html>
                        <head>
                          <title>'.$conf['site_name'].'</title>
                          <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
                          <meta http-equiv="Content-Language" content="Fr" />
                          <META name="Author" lang="fr" content="MagdiWeb" />
                          <META name="Copyright" content="© MagdiWeb" />
                        </head>
                        <body>
                          '.$mail_title.'<br>
                          '.$field_data['log'].'<br>
                          <img src="http://pi_timelapse/'.$conf['upload_dir'].'/'.$_FILES['img']['name'].'">

                        </body>
                      </html>';

      // envoie le mail
      $mail       = mailIt ('mon_adresse_mail@mail.fr', $mail_title, $mail_core);
      logIt('access', $mail_title);
    }
    else {
      logIt('access', 'unable to mv uploaded file '.$_FILES['img']['name']);
    }
  }
  else{
    logIt('access', 'error during file upload ...');
  }

}
else{
  logIt('access', 'wrong heartbeat token ...');
}

// écrit l'ensemble des messages/logs dans un fichier
writeLog ();

?>

Voici une screenshot du mail que je reçois sur mon smartphone lors de la réception d’un battement de coeur de la part du Pi (sur la photo on voit un bout de mon bureau,… c’est pour les tests 😉 :

photo

Dashboard

J’ai également prévu une page type dashboard qui affiche toutes les miniatures reçues avec les dates. Pour le moment, je ne publie pas cette page, mais lorsque le chantier commencera, je la synchroniserai avec ce blog afin que vous puissiez suivre en directe l’évolution des travaux, et constater que le dispositif fonctionne bien 🙂

Toujours aussi minimaliste, le but est simplement d’afficher l’ensemble des miniatures sous forme d’une mosaïque d’images.

<?php

header('Content-type: text/html; charset=utf-8');

require_once('conf.php');
require_once('func.php');

$session_id = sessionStart();
$db_connect = dbConnect();
$_REQUEST   = clearRequest ($_REQUEST);

if(!empty($_REQUEST['token']) && $_REQUEST['token'] == $conf['token']['index']) {

$heartbeats = selectAll ('heartbeat');

?>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
    <title><?php echo $conf['site_name']; ?></title>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
    <meta http-equiv="Content-Language" content="Fr" />
    <META name="Author" lang="fr" content="MagdiWeb" />
    <META name="Copyright" content="© MagdiWeb" />  
    <link rel="stylesheet" type="text/css" href="style.css" />
  </head>
  <body>
    <?php
      $nb_heartbeat = sizeof($heartbeats);
      echo '('.$nb_heartbeat.' beats)';
    ?>
    <table>
    <?php
      $c = 0;
      foreach($heartbeats as $heartbeat) {
        if($c == 0){echo '<tr>';}
        echo '<td>'.humanDate ($heartbeat['date'], 'full').'<br><img src="'.$conf['upload_dir'].'/'.$heartbeat['img'].'"></td>';
        $c++;
        if($c == 4){$c=0; echo '</tr>';}
      }
    ?>
    </table>

  </body>
</html>

<?php

}else{
  logIt('access', 'wrong index token ...');
}

writeLog ();

?>

Voici une screenshot de ce que cela donne pour l’instant (toujours avec des photos de test 😉 :

Screenshot-1

5 – Schéma global et utilisation

Le dispositif au complet se compose de trois éléments :

  • Le système de capture d’image
  • Le circuit de temporisation de l’alimentation
  • Un serveur de monitoring

schéma global Pi TimeLapse

Le système de capture d’image

Composé du Pi, d’une webcam, d’un dongle 3G et d’une batterie 5V, ce système se contente de prendre une photo à chaque démarrage, puis s’éteint immédiatement. Une fois sur trois, il envoi une requête au serveur de monitoring pour signifier que tout va bien 🙂

Le circuit de temporisation de l’alimentation

Ce circuit alimenté par une batterie 12V, pilote un relais électrique permettant de commander l’alimentation du Pi pendant 2 minutes toutes les 10 minutes, de 8h00 à 18h00, du lundi au vendredi.

Un serveur de monitoring

Un script PHP publié par un serveur web Apache permet d’enregistrer les requêtes POST envoyée par le Pi toutes les 30 minutes. Il réceptionne une miniature d’une la dernière photo prise, et enregistre l’heure exacte à laquelle il l’a reçu dans une base de données MySQL. Si tout est ok, il envoi un mail avec la date et la photo miniature, ce qui me permettra d’être continuellement averti du bon fonctionnement du système de capture d’image.

En parallèle, ce serveur web publie une page web, qui liste l’ensemble des miniatures ainsi enregistrées, ce qui permet d’avoir une vue d’ensemble du bon déroulement des choses sur une semaine.

Scénario d’utilisation

  1. Le circuit de tempo alimente le Pi qui boot immédiatement
  2. Une fois démarré, le Pi prend une phot et lance un shutdown
  3. Une fois sur trois (soit toutes les 30 minutes) le Pi envoi une requête sur un serveur de monotoring distant pour dire qu’il est toujours en vie
  4. Le circuit de tempo coupe l’alimentation du Pi après 2 minutes