Jenuino : Jenkins is watching you

L'intégration continue c'est bien, mais l'Extreme Feedback c'est mieux! :)

Le but de cet article est d'attiser votre curiosité et vous montrer qu'il est assez simple de rendre certains outils plus fun! Nous allons voir comment fabriquer un "totem" nous affichant le résultat des builds programmés sur un serveur Jenkins. J'ai baptisé ce projet : JENUINO

Avant tout, un aperçu du résultat :

Build OKBuild en coursBuild KO

Comme vous pouvez le voir, ce totem se compose :

  • D'un écran LCD affichant un petit message
  • D'une lumière colorée selon l'état du build
  • D'un bouton pour relancer un build

Mais qu'est-ce qui se cache dessous??... Et bien un Arduino!

Cette petite plateforme électronique OpenSource italienne permet de réaliser des montages en un tour de main, sans connaissance particulière en la matière.

Il sera relié au PC via un câble USB. Notre programme lui est configurable via une petite interface "maison" :

Et maintenant, on se lance!

Côté Java

Plusieurs éléments sont nécessaires pour accomplir ce montage : une librairie pour la communication via le port USB, l'API Jenkins pour accéder aux informations des projets et quelques éléments d'interface basiques.

Communication USB

Les ports USB étant des ports série (COM), nous allons utiliser la librairie RXTX. Elle se compose de deux éléments importants : le JAR bien sûr, mais également les librairies système (.dll sous Windows) permettant à la JVM d'utiliser les ports USB. Toutes les informations pour l'installation sont disponibles sur le site officiel. Ensuite, quelques lignes de code :


import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;

public class SerialCom implements SerialPortEventListener {
 SerialPort serialPort;

 private BufferedReader input;
 private OutputStream output;
 private static final int TIME_OUT = 2000;
 private static final int DATA_RATE = 9600;

 public void initialize(String portId) {
  try {
   // open serial port, and use class name for the appName.
   serialPort = (SerialPort) CommPortIdentifier.getPortIdentifier(portId).open(this.getClass().getName(),
     TIME_OUT);

   // set port parameters
   serialPort.setSerialPortParams(DATA_RATE, SerialPort.DATABITS_8,
     SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);

   // open the streams
   input = new BufferedReader(new InputStreamReader(
     serialPort.getInputStream()));
   output = serialPort.getOutputStream();

   // add event listeners
   serialPort.addEventListener(this);
   serialPort.notifyOnDataAvailable(true);
  } catch (Exception e) {
   System.err.println(e.toString());
  }
 }

 /**
  * This should be called when you stop using the port. This will prevent
  * port locking on platforms like Linux.
  */
 public synchronized void close() {
  if (serialPort != null) {
   serialPort.removeEventListener();
   serialPort.close();
  }
 }

 /**
  * Handle an event on the serial port. Read the data and print it.
  */
 public synchronized void serialEvent(SerialPortEvent oEvent) {
  if (oEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
   try {
    int inputLine = input.read();
    // flush com data
    input.ready();
   } catch (Exception e) {
    System.err.println(e.toString());
   }
  }
 }
}

La méthode initialize utilise le nom du port ("COM5" par exemple) pour initialiser les flux entrants et sortants, selon le débit DATA_RATE (9600 kbps par défaut pour l'USB). Cette classe est également capable de réagir aux évènements entrants grâce à l'interface SerialPortEventListener.

Lors du premier lancement, il se peut que vous soyez confrontés à l'erreur :

java.lang.UnsatisfiedLinkError: no rxtxSerial in java.library.path thrown while loading gnu.io.RXTXCommDriver
Exception in thread "main" java.lang.UnsatisfiedLinkError: no rxtxSerial in java.library.path
 at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860)
 at java.lang.Runtime.loadLibrary0(Runtime.java:845)
 at java.lang.System.loadLibrary(System.java:1084)

Cela signifie (sous Windows) qu'il faut copier "rxtxSerial.dll" sous JAVA_HOME/jre/bin. Il sera également nécessaire d'installer le driver Arduino, comme expliqué sur cette page.

Ensuite, il suffira de lire ou d'écrire dans les Stream de la même manière que pour l'écriture/lecture de fichier ou quelconque autre opération basique en Java.

L'interface de configuration

Je ne vais pas m'attarder sur ce point, car il ne s'agit que d'un exemple, basé sur :

  • java.awt.SystemTray : pour ajouter l'icône et son menu contextuel dans la zone de notifications
  • java.util.Timer : pour mettre en place une tâche de fond qui interroge à intervalles réguliers le serveur Jenkins et met à jour le totem

L'API Jenkins

Jenkins fournit la possibilité d'accéder et de manipuler les jobs via JSON et/ou XML et une API relativement simple. Voici donc un client Jenkins en quelque lignes :


import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

public class JenkinsClient {
 
 private final String API_URL = "api/xml";
 private String serverURL;
 private String job;
 
 public JenkinsClient(String url, String jobName) {
  if(url==null) {
   serverURL = "http://localhost:8080/jenkins/";
  } else {
   serverURL = url;
  }
  job = jobName;
 }

 public BuildState findLastBuildState() throws Exception {
  BuildState state = null;
  // every Hudson model object exposes the .../api/xml
  URL url = new URL(serverURL+"job/"+job+"/lastBuild/"+API_URL);
  
  // read it into DOM.
  Document dom = new SAXReader().read(url);
  
  String elementresult = dom.getRootElement().elementText("result");
  if(elementresult!=null) {
   state = BuildState.fromValue(elementresult.toString());
  } else if(dom.getRootElement().elementText("building")!=null) {
   state = BuildState.fromValue("building");
  }
  return state;
 }
 
 public boolean jobExists() {
  boolean exists = false;
  try {
   // every Hudson model object exposes the .../api/xml
   URL url = new URL(serverURL+API_URL);
   
   // read it into DOM.
   Document dom = new SAXReader().read(url);
   
   // scan through the job list and print its status
   for (Element jobElt : (List) dom.getRootElement().elements("job")) {
    exists |= jobElt.elementText("name").equals(job);
   }
  } catch (Exception e) {
   e.printStackTrace();
  }
  return exists;
 }
 
 public void triggerBuild() {
  try {
   URL url = new URL(serverURL+"job/"+job+"/build");
   URLConnection conn = url.openConnection();
   InputStream is = conn.getInputStream();
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
}

Avec ceci, nous serons capables de vérifier l'existence d'un projet sur le serveur, de récupérer l'état du dernier build et en déclencher de nouveaux.

Côté Arduino

Pour cette seconde étape, il faut réaliser le montage électronique et programmer le micro-controlleur de la plateforme afin de réagir à nos évènements via le port USB.

Hardware

Ci-dessous un schéma expliquant l'exemple de mon montage :

Inspiré par les montages publiés sur le net, celui-ci est bien moins complexe qu'il n'en a l'air. La présence de la platine d'essai au centre est justifiée par le fait que ce montage n'est que temporaire et il est généralement plus simple d'utiliser ce système que de vouloir raccorder directement les composants entre eux, surtout lorsqu'il faut gérer la "stabilité" sans soudure...

Alors un peu de dextérité et d'ingéniosité, et le tour est joué!

Software

Au niveau logiciel, Arduino se base sur le langage Processing, proche du C mais basé sur un environnement Java, donc très facilement accessible. Un programme de base se compose de deux méthodes clés :

  • setup() : appelée au démarrage de la plateforme (mise sous tension) pour configurer et initialiser le programme.
  • loop() : appelée en boucle et en permanence tout au long de l'exécution.

Voici un bref aperçu de l'environnement de développement, tout aussi simple que le langage :

Mon but ici étant simplement de piquer votre curiosité, je ne vais pas m'étendre plus sur la présentation du langage et de l'environnement, tout ceci étant très facile à trouver et très bien expliqué sur les sites français ou anglais officiels. Je vous invite tout de même à prendre quelques minutes pour regarder cette vidéo de présentation du projet Arduino par un de ses fondateurs. Je suis sûr que cela vous donnera envie!

Pour ceux que cela intéresse, mon projet est disponible sur Sourceforge : Jenuino

Enfin, si vous voulez vous lancer, pour ce qui est de l'achat du matériel je vous conseil le site Deal Extreme, proposant des kits de démarrage assez complets et a bon prix (attention tout de même aux délais de livraison qui peuvent être longs).


Fichier(s) joint(s) :

0 commentaires: