Arduino : XBee Ethernet data logger

Après plusieurs semaines de galère, j'ai décidé de rassembler ici tous les éléments qui m'ont permis, tant bien que mal, de mettre en place l'installation suivante :

D'un côté, un module XBee transmettant les informations d'un capteur de température (LM35). De l'autre, un second XBee relié à un Seeduino ATMega 2560 (similaire à un Arduino) qui enregistre toutes les mesures reçues sur une carte SD et expose le fichier créé via une connexion Ethernet. Voici le schéma du montage :

Avant d'aller plus loin, il est indispensable de faire un point sur la notion de SPI afin de comprendre le fonctionnement de notre montage.

SPI est un bus de communication basé sur les principes de Maitre et Esclave. Il est composé de 4 canaux. Pour ne pas plagier un très bon cours existant sur le site rocketnumbernine, voici en quelques mots son fonctionnement :

Each end of a SPI connection is acting in one of two roles - Master or Slave. The master is responsible for initiating and controlling the communication.

SPI Master/Slave Connections

The basic mode of operation is very simple: When the master wishes to initiate transfer of data:
  1. It sets the SS (Slave Select - often called CS - chip select) pin low to tell the slave that communication is about to start
  2. The master writes a bit of information onto the MOSI (Master Out Slave In) wire (sets it to 0 or 1) and the slave does the same on the MISO wire (either of these can be omitted if the data transfer is one way)
  3. As the master ticks the clock line SCLK it will read the value of MISO (Master In Slave Out) wire (which the Slave has written) and the slave will read the value of the MOSI wire (whether the data is sampled as the clock rises or falls depends on which mode is in operation)
  4. The process is repeated from (b), transferring a bit of data on each pulse of the clock until all data is transferred

Ce bus SPI est accessible via les broches ICSP (Arduino UNO et Mega 2560) ET les broches 11,12,13 (pour le UNO) ou 50,51,52 (pour le Mega 2560).

Un élément important est le Chip Select : il s'agit de la broche qui permet de choisir le périphérique cible de l'échange. Pour désactiver un périphérique, il faut passer la broche en niveau haut et pour l'activer, en niveau bas. Un seul périphérique ne peut être activé à la fois car les trois broches de communication MOSI, MISO et SCK sont partagées.

Sur l'Arduino UNO (Revision 3), le Mega 2560 (Rev 3) et sur le shield Ethernet (Rev 3), on trouve le CS :

  • sur la broche A4 pour la carte SD
  • sur la broche A10 pour l'Ethernet

C'est grâce à cette correspondance des broches qu'il est possible de simplement empiler les cartes sans avoir à faire de connexions supplémentaires. Il est tout de même possible, si vous ne souhaitez pas empiler les cartes, de relier les broches comme indiqué sur le schéma. Attention cependant, ceci est impossible sur le shield Ethernet car seules les broches CS sont utilisées et reliées directement à l'ICSP. Toutes les autres broches ne servent qu'à transférer les connexions à une éventuelle autre carte supperposée.

Revenons donc à notre projet. La carte Ethernet est apposée sur l'ATMega 2560, le module XBee principal (coordinateur) relié directement au Serial1 : broches RX1 et TX1 (croisées avec les RX/TX du XBee).

Côté logiciel maintenant : l'application doit, au démarrage, aller lire un fichier de configuration sur la carte SD et initialiser une connexion Ethernet. Pour bien démarrer, voici le code de la fonction setup() :


void setup() {
 pinMode(10, OUTPUT);
 pinMode(4, OUTPUT);
 // 
 pinMode(53, OUTPUT);
 digitalWrite(53, HIGH);
 /* disable all slaves */
 // disable Ethernet
 digitalWrite(10, HIGH);
 delay(5);
 // disable SD
 digitalWrite(4, HIGH);
 delay(5);

 /* read config file */
 // init SD
 boolean sd = SD.begin(4);
 delay(5);
 // ... read file ...
 // disable SD
 digitalWrite(4, HIGH);
 delay(5);
}

A noter qu'il est très important de désactiver à la main l'Ethernet après son initialisation car la librairie ne le fait pas! Sur un ATMega 2560, il faut également obligatoirement définir la broche de Hardware SS (53) en sortie.

Ensuite, afin que le programme puisse à la fois recevoir en continu des paquets de données XBee à écrire sur la carte SD et assurer son rôle de serveur web, voici, sous forme algorithmique, le déroulement du code à mettre en place :

void loop() {
 - Vérifier la présence d'un packet XBee
  - si un paquet a été reçu :
   - traiter les informations du paquet 
   - désactiver l'Ethernet : digitalWrite(10, HIGH)
   - activer la carte SD : digitalWrite(4, LOW)
   - écrire sur la carte SD
   - désactiver la carte SD : digitalWrite(4, HIGH)
 - Vérifier la réception d'une requete Ethernet :
  - si une requete a été reçue :
   - désactiver la carte SD : digitalWrite(4, HIGH)
   - activer l'Ethernet : digitalWrite(10, LOW)
   - générer la réponse HTTP
   - fermer la connexion client
   - désactiver l'Ethernet : digitalWrite(10, HIGH)
}

Normalement, la gestion de l'activation/désactivation de chaque périphérique SD ou Ethernet est gérée en interne par les librairies correspondantes, mais je vous conseille de ne pas hésiter à le faire également manuellement afin de renforcer votre programme (on n'est jamais mieux servi que par soi-même!).

Un conseil supplémentaire : il est préférable d'ajouter quelques délais après chaque dés/activation de périphérique (via la méthode delay()), au moins pendant la phase de développement, afin d'éviter tout conflit de communication sur le bus SPI (par les broches partagées) qui pourrait engendrer, par exemple, la destruction de la carte SD (qui arrive plus vite qu'on ne le croit!).

Ci-dessous sont décrites les erreurs les plus communes qui peuvent être rencontrées :

  • Impossible de démarrer la carte SD :
    Si SD.begin() renvoie false, il faut dans un premier temps vérifier les connexions SPI (MOSI, MISO) dans le cas où elles auraient été réalisées autrement que par les broches ICSP et dans un second temps s'assurer que seule sa broche CS est active.
  • Impossible de PING la carte Ethernet :
    Les requêtes ping répondent "Impossible de joindre l'hôte" ("Host unreachable") et la LED TX de la carte ne s'allume jamais : vérifier également que seule la broche CS de la carte Ethernet est active et qu'aucun autre périphérique ne tente d'accéder au bus SPI.

Vous voilà prêts! J'ai essayé de regrouper ici les connaissances de base dont j'ai eu besoin et pour lesquelles j'ai du écumer nombre de blogs et forums. J'espère que tout cela vous sera utile!


Fichier(s) joint(s) :



Analyser une interface Swing

Intervenir sur une application Swing peut vite s'avérer très complexe si celle-ci est basée sur une importante imbrication de composants et layouts personnalisés.

Afin de se faciliter la tâche, il existe Swing Explorer. Disponible sous forme de plugin (Eclipse, Netbeans...) ou autonome, il permet d'explorer l'arbre des composants graphiques au moment de l'exécution de l'application :

Comme vous pouvez le voir, la partie gauche contient les arbres de tous les objets Swing instanciés. Il est alors possible de double-cliquer dessus pour en afficher un aperçu dans la partie droite.

Il est alors possible de sélectionner à la souris chaque composant pour entrer dans leur détail.

C'est donc l'outil idéal pour retrouver n'importe quel composant graphique instancié dans une application, sans avoir à utiliser de point d'arrêt ou modification pas-à-pas.

Il dispose même d'un Player qui permet de rejouer la création et la mise en place des objets telles qu'exécutées par Swing :

Sur ce, bon debug! :)


Fichier(s) joint(s) :



Dimensionner son microcontrolleur

Une des premières recommandations lors du choix d'un microcontrolleur est de porter une attention particulière à la mémoire dont il dispose. La plus contraignante importante est la mémoire Flash, qui stocke le code du programme mis en place.

Mais comment estimer la mémoire nécessaire pour un programme? Pas de secret, il faut le coder! Il est en effet très important d'essayer de réfléchir à l'avance au nombre de librairies externes à importer, de fonctionnalités à implémenter et à la volumétrie de code que cela pourrait représenter. En se donnant évidemment une grande marge d'évolution.

Pour cela, des IDEs comme Atmel Studio peuvent vous indiquer, après compilation, la taille de votre programme (via notament l'outil GCC avr-size). Prenons un exemple :

Un projet ayant choisi un Arduino UNO (ATmega328 - 32Ko Flash) et nécessitant :

  • Une librairie pour carte SD
  • Une librairie pour modules radio XBee
  • Quelques centaines de lignes de code

Voici le résultat de la compilation :

Comme vous pouvez le voir, nous sommes déjà bien au delà de la taille limite...

Quelles sont les pistes d'amélioration? Parmi les plus courantes :

  • réduire au maximum tous les types de données : gain rarement au dessus de 2%
  • jouer sur les optimisations du compilateur (option -O) : gain espéré autour de 2 à 3% (avec l'effet pervers de souvent nuire au debug)

Certains vous diront qu'il faut se passer au maximum des librairies externes pour n'implémenter que le code nécessaire... Je laisse cela à ceux qui aiment réinventer la roue! :)

Donc nous sommes encore loin du compte... Ainsi, sauf si je ne m'abuse, la seule solution est bien de changer de matériel! La famille Atmel propose des microcontrolleurs allant jusqu'à 256 Ko de mémoire Flash, ce qui laisse déjà plus d'amplitude.

Il existe donc d'autres plateformes basées sur ce matériel :

Petite précision : pour continuer à s'amuser sans se ruiner, n'oubliez pas qu'il existe d'autres marques que Arduino, souvent moins onéreuses mais tout à fait compatibles, tant du point de vue matériel que logiciel, puisqu'utilisant les mêmes composants.

Et pour les plus courageux, vous pouvez même essayer de fabriquer vous-mêmes votre plateforme :

Tout cela pour vous dire que si l'Arduino Uno est excellent pour démarrer, si vous avez l'intention d'aller plus loin que faire clignoter quelques LED, il ne faudra pas hésiter à se diriger vers du matériel un peu plus généreux!


Fichier(s) joint(s) :



Présentation de l'AVR Dragon

Comme évoqué dans mon précédent article, la carte AVR Dragon de Atmel permet de débugger et monitorer du code s'exécutant sur les microcontrôleurs AVR.

Mais commençons par le commencement, où se le procurer? En effet, ceci est bien moins simple qu'il n'y parait. Seuls quelques sites proposent de le commander à des prix corrects :

  • Radiospares : le meilleur prix trouvé mais semble-t-il réservé aux professionnels... Impossible de le commander autrement.
  • Digikey : cette fois, c'est plutôt les détails sur la livraison qui font défaut : prix, délais... Reconnu mais pas très clair à mon goût.
  • Enfin, Farnell : un prix de base à peine plus élevé, des frais de transport assez chers, mais une livraison garantie en 24h (j'ai testé).

L'intérêt principal de cet outil est qu'il est largement présenté et décrit sur le net, régulièrement utilisé dans les tutoriels et forcément très bien intégré à l'environnement Atmel, notamment au debugger de Atmel Studio.

Mais avant d'en arriver là, il faudra transpirer encore un peu!

La carte c'est bien, mais comment l'utiliser? Là encore, fouillis sur la toile : ISP, JTAG, HE10... Ouf! Alors pour faire clair, voici ce que j'ai pu en tirer :

Pour un Arduino UNO, la seule interface disponible est l'ISP, composé de 2x3 broches. Malheureusement, ce type de cable est assez difficile à trouver, même sur le net. Et franchement, payer 5€ de frais de port pour un cable à 30cts, non merci! Alors oui, incroyable mais vrai, il va falloir se FABRIQUER le fameux cable...

Voici comment j'y suis parvenu : des connecteurs type HE10 femelle, un nappe de 10 connecteurs, des gaines thermo-rétractables et du courage. Voici ce que cela peut donner (je ne suis pas expert en soudure...) :

Et voici le branchement espéré :

C'est parti? Eh non! Car en l'état, l'interface ISP ne pourra pas reprogrammer le micro-contrôleur : en effet, la carte Arduino possède un capaciteur branché sur le système de RESET, utilisé pour réinitialiser le bootloader lors de la programmation par USB. Et ce capaciteur interfère avec l'usage des modes de debug ISP et DebugWire. Il va donc falloir désactiver ce capaciteur!

Vous trouverez ici une excellente explication de la marche à suivre : Adam Hilltop

Après cette opération, la programmation automatique via la prise USB ne sera plus possible. Il faudra penser à actionner manuellement le bouton RESET de l'Arduino juste avant chaque nouvel upload (ou reconstituer le capaciteur!).

Un autre exemple pour le Duemilanove : Awtfy

Cette fois-ci, normalement, vous avez tout le nécessaire pour commencer à débugger votre code! En attendant mes propres tests, voici encore un peu de lecture :

Go ahead!


Fichier(s) joint(s) :



Debugger avec Arduino

Cet article (un peu long) a pour but de donner un point de vue global sur l'environnement Arduino et les différents outils disponibles, fruit de longues heures de recherches. Aucune vocation à être exhaustif, mais plutôt de permettre de se situer et savoir par où commencer selon vos besoins.

Ayant passé le cap du newbie avec Arduino et ayant donc atteint les limites de l' "IDE" fourni, je cherchais maintenant un moyen d'utiliser un VRAI IDE afin de pouvoir développer de manière plus complexe en C++ (créer des structures, jouer avec le pointeurs) et bénéficier des avantages habituels d'un tel outil : coloration syntaxique, auto-completion, optimisation et... DEBUG!!

Mais tout d'abord, quelles sont les limites de l'outil de développement proposé par la distribution Arduino? En réalité, son intérêt est d' "arranger" le code saisi afin de le rendre compatible à un environnement classique C/C++ (car c'est bien ce qui se cache derrière). Mais on arrive vite au point où cela ne nous "arrange" plus du tout : des erreurs de compilation apparaissent alors que le code est bon (compilable par ailleurs).

De la même façon, l'outil permet de faciliter la programmation de la platine en masquant le travail de la "toolchain". D'ailleurs, petite parenthèse, car souvent sur le net ceci semble inné, mais voici une petite explication de ce qu'est cette AVR Toolchain :

La toolchain AVR — Chaque outil de la chaîne de développement possède un rôle spécifique. Les productions de chaque outil intermédiaire servant à alimenter le suivant:
  • Le compilateur traduit en assembleur les fichiers sources écrit dans un langage de haut niveau;
  • L'assembleur transforme le code assembleur (issu de la compilation ou écrit manuellement par le programmeur) en fichier objet contenant uniquement du code binaire;
  • L'éditeur de liens (linker) rassemble les différents fichiers objets binaires et les bibliothèques en un seul fichier exécutable. L'exécutable peut être directement lancé sur un simulateur ou servir pour le débogueur;
  • Pour la programmation d'un micro-contrôleur, le programme object copy extrait de l'exécutable le code machine sous un format prêt à télécharger sur la cible;
  • Le programmeur est chargé de transférer le code vers la cible.
source : http://www.chicoree.fr/w/USnooBie#GNU_AVR_Toolchain

Mais rentrons dans le vif du sujet, quelles sont les possibilités qui s'offrent à nous pour développer en C/C++ orienté AVR (microcontrôleurs)?

Commençons par le commencement...

Comme nous l'a montré la présentation de la Toolchain, l'outil de base qui va nous permettre de debugger notre projet est donc AVR-GDB (la spécialisation pour AVR du debugger C/C++ nommé GDB).

Après lecture de quelques docs explicatives (pas si faciles à trouver) sur son fonctionnement ici et , on comprend vite qu'il est assez complexe, pouvant être lancé directement à partir d'un fichier compilé ou en mode serveur pour se connecter à un simulateur démarré séparément (voir plus bas).

Les IDEs

Comme tout bon développeur Java, je me suis tout d'abord demandé si mon bon vieux compagnon de route avait de quoi répondre à mes besoins : après quelques recherches sur le net, cette option semble assez complexe. De bonnes connaissances sont à prévoir avant de réussir à bien configurer Eclipse.

Puis, en continuant mes recherches sur le net, je rencontre Code::Blocks, un IDE Open Source, qui semble rencontrer un petit succès auprès des amateurs d'Arduino, surtout grâce à son plugin spécialisé. Après un test rapide, il semble un peu "léger" et je n'ai pas vraiment envie d'essayer de prendre un main un nouvel éditeur.

Continuons les recherches... Voici maintenant Atmel Studio : un IDE entièrement dédié au développement sur microcontrôleur puisque édité par la société qui fabrique un grande majorité des plus usités (notamment la famille ATMega que l'on retrouve sur les Arduino). Génial, oui, mais pas des plus simples à appréhender puisque très spécialisé. De très bon tutoriels un peu partout sur le net donnent heureusement souvent des informations essentielles pour s'en sortir. Gardons le dans un coin et essayons autre chose.

La dernière possibilité restante, Netbeans. Déjà très efficace pour le développement C/C++, sa configuration pour AVR semble plus aisée et largement explicitée comme ici ou ou encore par ici.

Cette piste semblant intéressante, allons plus loin. Après avoir passé quelques heures à m'arracher les cheveux pour faire fonctionner le compilateur C++, le linker, l'auto complétion etc (pourquoi tant de haine?!!!), et alors que la lumière surgissait au bout du tunnel et que j'allais enfin pouvoir commencer à coder, voici que Netbeans semble ne pas être très ami avec AVR-GDB... Quand ce dernier arrive à se lancer, aucun de mes points d'arrêts ne fonctionnent dans Netbeans... Tant pis, essayons d'avancer.

Petit aparté : une alternative aux IDEs classiques est Codebender, un outil de développement collaboratif en ligne. A essayer.

Les simulateurs/émulateurs

Une autre question me vient à l'esprit : plutôt que remettre en place tout mon montage chaque fois que je veux tester mon code, ne serait-il pas possible de simuler le comportement de l'Arduino sur le PC et ainsi gagner du temps?

Sur ce sujet là il existe pléthore de solutions, difficile de s'y retrouver.

Tout d'abord SimulAVR. Il est embarqué avec WinAVR et même présent dans la suite d'outils Arduino. Il se lance à partir d'un fichier ".elf" (produit par la compilation du projet C++) et sert de "serveur" sur lequel peut se connecter AVR-GDB pour exécuter le code, comme expliqué ici. Sa présentation très séduisante (spécialisé AVR...) laisse miroiter des fonctionnalités intéressantes mais très vite après quelques recherches on apprend qu'il n'est en réalité plus maintenu depuis quelques années et très limité...

Nous retrouvons ensuite Atmel Studio : il dispose également d'un simulateur, très souvent décrit et clairement expliqué. Après quelques tests et diverses galères, cette solution semble la plus probante : c'est bien le seul outil qui jusqu'ici m'a permis de dérouler pas à pas mon code. Mais (il en faut bien un), une fois encore les possibilités offertes par ce simulateur sont limitées : impossible de gérer une carte SD ou autre périphérique.

Il existe bien d'autres possibilités, mais hors IDE, comme le simulateur de Virtronics, payant mais pas excessif et très prometteur, ou encore Virtual Breadboard. Mais chacun avec ses inconvénients, souvent trop orienté Arduino ou pas assez, restreignant les possibilités de gérer un gros projet.

Comme vous pouvez le constater, l'écosystème Arduino est très complexe et il est assez difficile de savoir par où commencer et quels sont les bons outils pour des besoins bien précis.

Donc, si vous avez réussi à lire jusqu'ici et pour résumer, que faire? : pour rappel, mon besoin premier étant d'avoir la faculté de suivre l'exécution de mon code pour le débugger, il ne me reste plus qu'une solution, du côté matériel.

Voici donc AVR Dragon! Il s'agit d'une carte créée par la société Atmel elle-même afin d'offrir des fonctionnalités de debug et monitoring du code s'exécutant sur AVR. Toute une histoire!

Pour ne pas faire trop long, je présenterai cette carte dans un prochain article. D'ici là, n'hésitez pas à me faire des retours sur vos diverses expériences avec le développement pour Arduino.


Fichier(s) joint(s) :



Gestion des Exceptions, mais pas trop n'en faut...

Les gestions des erreurs au sein d'une application est souvent un point sensible, voire critique. Nous avons tous déjà lu tout un tas d'articles et conseils sur les bonnes pratiques, comme par exemple :

Mais un point important qui est pourtant bien moins évoqué, est le coût des Exceptions : en effet, les méthodes de création des objets Exception, comme fillInStackTrace() sont coûteuse en matière de charge CPU, comme le démontre cet article (que je vous recommande fortement) : Performance Impact of Exceptions

Un autre exemple plus pragmatique démontre par l'exemple ces affirmations : The Cost of an Exception.

Pour résumer, ce qu'il faut garder en mémoire :

Getting the stack trace of an exception had a 10x higher impact on the performance than just catching and throwing them. So while stack traces help to understand where and possibly also why a problem occurred, they come with a performance penalty.

A bon entendeur!


Fichier(s) joint(s) :



Spring et DWR

Direct Web remoting est un framework Ajax permettant un lien direct entre Javascript et Java coté serveur.

D'apparence assez simple, son intégration avec Spring peut rapidement devenir compliquée, notamment à cause de la gestion des namespaces dans le fichier XML de configuration. En effet, les versions récentes de DWR fournissent un namespace facilitant l'écriture des éléments spécifiques :


<dwr:controller id="dwrController" debug="true"/>
<dwr:configuration>...</dwr:configuration>

Mais puisque tout n'est pas rose, j'ai été confronté à une erreur récurrente au démarrage du contexte Spring : No bean named '__dwrConfiguration' found...

Quelque soit la version du namespace utilisé, pas moyen de m'en sortir... Voici donc comment revenir à une configuration plus simple basée sur des éléments XML plus classiques :

application-context-dwr.xml :


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dwr="http://www.directwebremoting.org/schema/spring-dwr"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                           http://www.directwebremoting.org/schema/spring-dwr
                           http://www.directwebremoting.org/schema/spring-dwr-2.0.xsd">
    
    <dwr:url-mapping />

    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />

    <bean id="myDwrServerBean" class="my.package.MyDwrServerBean">
        <dwr:remote javascript="serverBridge"></dwr:remote> <!-- nom de l'objet javascript utilisable -->
    </bean>


    
    <bean id="__dwrController" class="org.directwebremoting.spring.DwrController"> 
        <property name="configurators">
         <list>
            <ref bean="__dwrConfiguration"/>
         </list>
      </property>
      <property name="debug" value="true"/>
    </bean>
    
   <bean id="__dwrConfiguration" class="org.directwebremoting.spring.SpringConfigurator">
        <property name="creators">
            <map>
                <entry key="firstBean">
                    <bean class="org.directwebremoting.spring.CreatorConfig">
                        <property name="creator">
                            <bean class="org.directwebremoting.spring.BeanCreator">
                                <property name="bean" ref="myDwrServerBean"/>
                                <property name="javascript"><value>serverBridge</value></property>
                            </bean>
                        </property>
                    </bean>
                </entry>
            </map>
        </property>
        <property name="converters">
            <map>
                <entry key="my.package.MyDwrServerBean">
                    <bean class="org.directwebremoting.spring.ConverterConfig">
                        <property name="type">
                            <value>bean</value>
                        </property>
                    </bean>
                </entry>
            </map>
        </property>
    </bean>
</beans>

Il est également possible de configurer DWR via un bean dans le contexte Spring en utilisant le FluentConfigurator.

En espérant vous avoir fait économiser quelques heures de recherches!


Fichier(s) joint(s) :



Gestion de l'authentification sur Google App Engine

Lors de la création d'une application sur la plateforme GAE, il est possible de configurer l'usage des comptes Google pour s'y connecter, comme décrit ici : Configuration de l'authentification de votre application

En mode développement, le serveur de test simule automatiquement cette utilisation de compte Google et met en place un système de redirection vers une page de connexion spécifique (https://developers.google.com/appengine/docs/java/tools/devserver#Using_Users) :

L'utilisateur connecté sera alors accessible simplement avec :


import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.appengine.api.users.User;
...
UserService userService = UserServiceFactory.getUserService();
User user = userService.getCurrentUser();

Pour ce qui est de la déconnexion, il faut créer une redirection vers l'URL gérée par Google. Pour ce faire, voici un exemple de servlet :


import com.google.appengine.api.users.UserServiceFactory;
import java.io.IOException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.logging.Level;
import java.util.logging.Logger;

public class LogoutController {

    @RequestMapping(method = RequestMethod.GET)
    public void logout(HttpServletRequest request, HttpServletResponse response) {

        SecurityContextHolder.clearContext();

        // Remove the cookie
        Cookie terminate = new Cookie(
                TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY,
                null);
        terminate.setMaxAge(-1);
        terminate.setPath(request.getContextPath() + "/");
        response.addCookie(terminate);
        try {
            response.sendRedirect(UserServiceFactory.getUserService().createLogoutURL("/"));
        } catch (IOException ex) {
            Logger.getLogger(LogoutController.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

Hope this helps!


Fichier(s) joint(s) :

Configurer une application web pour Google App Engine

GAE requiert une configuration bien précise, voir spécifique, de l'application web destinée à être déployée sur ces serveurs. Je vais essayer de détailler ci-dessous les éléments que j'ai rencontré.

Descripteur de déploiement

En plus du fichier web.xml commun à toutes les applis web, GAE demande la mise en place d'un nouveau fichier de description spécifique, appengine-web.xml présenté dans ce chapitre de la documentation.

Validation des fichiers XML

Le serveur de test peut parfois poser des problèmes lors de la phase de validation Spring des fichiers XML. Pour la désactiver, il faut indiquer dans le fichier web.xml une classe spécifique de configuration :


<context-param>
 <param-name>contextClass</param-name>
 <param-value>my.package.MyXmlWebApplicationContext</param-value>
</context-param>

Pour ce besoin, son contenu est assez simple :


import com.google.appengine.api.utils.SystemProperty;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.web.context.support.XmlWebApplicationContext;

public class MyXmlWebApplicationContext extends XmlWebApplicationContext {
    @Override
    protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {
        super.initBeanDefinitionReader(beanDefinitionReader);
        if (SystemProperty.environment.value() == SystemProperty.Environment.Value.Development) {
            beanDefinitionReader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_NONE);
        }
    }
}

persistence.xml

Afin de lier notre application au datastore de Google, voici un exemple basique de fichier de persistence :


<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
        http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">

    <persistence-unit name="transactions-optional">
        <provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider>
        <properties>
            <property name="datanucleus.NontransactionalRead" value="true"/>
            <property name="datanucleus.NontransactionalWrite" value="true"/>
            <property name="datanucleus.ConnectionURL" value="appengine"/>
        </properties>
    </persistence-unit>

</persistence>

Déclaration du bean "entityManagerFactory" via Spring

Afin de rendre effective la connexion au datastore, il est nécessaire d'ajouter au fichier application-context.xml de Spring :


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    <bean id="entityManagerFactory" class="com.my.MyEntityManagerFactory"/>
</beans>

Voici le contenu de la classe indiquée :


import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceException;
import org.springframework.orm.jpa.LocalEntityManagerFactoryBean;

public class MyEntityManagerFactory extends LocalEntityManagerFactoryBean {
    @Override
    protected EntityManagerFactory createNativeEntityManagerFactory() throws PersistenceException {
        return EMF.get();
    }
}

La classe EMF est tirée des préconisations Google (https://developers.google.com/appengine/docs/java/datastore/jpa/overview?hl=fr) :


import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public final class EMF {
    private static final EntityManagerFactory emfInstance =
        Persistence.createEntityManagerFactory("transactions-optional");
    
    private static EntityManager entityManager = emfInstance.createEntityManager();

    private EMF() {}

    public static EntityManagerFactory get() {
        return emfInstance;
    }

    public static EntityManager getEntityManager() {
        return entityManager;
    }
}

Avec ces éléments, vous devriez pourvoir démarrer votre application rapidement.


Fichier(s) joint(s) :

Maven, Netbeans et Google App Engine

Comme promis dans mon précédent article, voici quelques explications sur la configuration du POM requise pour créer une application compatible avec GAE.

Les éléments présentés ci-dessous ne le sont qu'à titre d'exemple, le but étant essentiellement de les regrouper de manière accessible.

Server de test

Lors de la phase de développement, il est possible d'utiliser Maven pour démarrer un server App Engine de test basé sur Jetty :


<plugin>
 <groupId>org.mortbay.jetty</groupId>
 <artifactId>jetty-maven-plugin</artifactId>
 <version>8.0.1.v20110908</version>
 <configuration>
  <scanIntervalSeconds>1</scanIntervalSeconds>
  <stopKey>stop-jetty</stopKey>
  <stopPort>9999</stopPort>
 </configuration>
</plugin>
<plugin>
 <groupId>com.google.appengine</groupId>
 <artifactId>appengine-maven-plugin</artifactId>
 <version>1.8.0</version>
</plugin>

Configuration de Netbeans

Pour démarrer ce serveur, il faut également configurer les actions du projet :

Activer le debug du serveur de test avec Netbeans

Dans un profil par exemple :


 <profile>
 <id>appengine</id>
 <properties>
  <env>appengine</env>
 </properties>
 <activation>
  <activeByDefault>true</activeByDefault>
 </activation>
 <build>
  <plugins>
   <plugin>
    <groupId>com.google.appengine</groupId>
    <artifactId>appengine-maven-plugin</artifactId>
    <version>1.8.0</version>
    <configuration>
     <jvmFlags>
      <jvmFlag>-Xdebug</jvmFlag>
      <jvmFlag>-Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n</jvmFlag>
     </jvmFlags>
    </configuration>
   </plugin>
  </plugins>
 </build>
</profile>

De cette manière, après le lancement du serveur de test avec la commande mvn appengine:devserver, il sera possible d'attacher un debugger au port du processus distant.

Dépendances à supprimer

Suite aux explications données dans l'article précédent, les dépendances ci-dessous ne sont plus nécessaires :

  • mysql (ou autre JAR de driver/connector de SGBD)
  • ehcache
  • aspectj
  • hibernate
  • spring AOP
  • spring tx (pour les transactions)

Couche de persistence

Voici les dépendances nécessaires :


<dependency>
 <groupId>com.googlecode.objectify</groupId>
 <artifactId>objectify</artifactId>
 <version>4.0b3</version>
</dependency>
<dependency>
 <groupId>com.google.appengine.orm</groupId>
 <artifactId>datanucleus-appengine</artifactId>
 <version>2.0.1.1</version>
 <!-- Need to exclude the enhancer since it interfere with the enhancer plugin.
 <exclusions>  
  <exclusion>  
   <groupId>org.datanucleus</groupId>  
   <artifactId>datanucleus-enhancer</artifactId>  
  </exclusion>  
 </exclusions> -->  
</dependency>
<dependency>
 <groupId>org.datanucleus</groupId>
 <artifactId>datanucleus-core</artifactId>
 <version>3.0.11</version>
</dependency>
<dependency>
 <groupId>org.datanucleus</groupId>
 <artifactId>datanucleus-api-jpa</artifactId>
 <version>3.0.11</version>
</dependency>
<dependency>
 <groupId>org.apache.geronimo.specs</groupId>
 <artifactId>geronimo-jpa_2.0_spec</artifactId>
 <version>1.0</version>
</dependency>
<dependency>
 <groupId>javax.jdo</groupId>
 <artifactId>jdo-api</artifactId>
 <version>3.0</version>
</dependency>

Pour activer le tissage des classes métier par Datanucleus lors de la compilation (enhancer), voici les plugins à déclarer dans la section build :


<plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-surefire-plugin</artifactId>
 <version>2.14.1</version>
</plugin>
<plugin>
 <groupId>org.datanucleus</groupId>
 <artifactId>maven-datanucleus-plugin</artifactId>
 <version>3.0.2</version>
 <configuration>
  <api>JPA</api>
  <verbose>true</verbose>             
  <mappingIncludes>**/domain/*.class</mappingIncludes>
  <fork>false</fork>
 </configuration>
 <executions>
  <execution>
   <phase>process-classes</phase>
   <goals>
    <goal>enhance</goal>
   </goals>
  </execution>
 </executions>
</plugin>

Dans l'article suivant, je présenterai les autres fichiers de configuration nécessaires, notamment la configuration de Spring pour la persistence.


Fichier(s) joint(s) :

Migration d’une application vers Google App Engine

Cet article ouvre une série de billets ayant pour but de condenser et expliquer l'ensemble des difficultés que j'ai rencontrées lors du déploiement d'une application web standard vers GAE.

L'application initiale utilise quelques technologies classiques du web : Ajax (framework DWR), JSP, MySQL, Hibernate, JPA, Spring (AOP, MVC, DAO).

Configuration du projet

La plateforme GAE requiert l’ajout de dépendances vers :

  • API App Engine : pour l’accès aux fonctionnalités spécifiques (récupération de l’utilisateur connecté...)
  • Datanucleus (ORM) : le data store de App Engine étant du NoSQL, Hibernate n’est plus utilisable.
  • Objectify (recommandé) : couche d’abstraction de persistence.

Spring

GAE préconise plusieurs optimisations de la configuration Spring ainsi que certains usages spécifiques à sa plateforme, détaillés ci-dessous.

source : https://developers.google.com/appengine/articles/spring_optimization

L’application initiale est basée sur la détection automatique de beans typés par interfaces : la configuration du contexte ci-dessous permet à Spring de parcourir les packages indiqués pour trouver les implémentations à injecter :

<context:component-scan base-package="tudu.service"/>

Sur GAE, ce système est déconseillé (pour des raisons de performances de la plateforme) et il est recommandé de préférer un usage plus "simple" de l’injection, via une déclaration explicite des beans, par exemple :


<bean id="userService" class="tudu.service.impl.UserServiceImpl"/>
<bean id="tenantService" class="tudu.service.impl.TenantServiceImpl"/>
...

De la même façon, les dépendances doivent de préférence être entièrement configurées dans le fichier de contexte plutôt qu’automatiquement détectées par l’annotation @Autowired, par exemple:

Pour le lien entre un controller et un DAO du type :


public class MovieController {
  MovieDaoInterface movieDao;
  public void setMovieDao(MovieDaoInterface movieDao) {
    this.movieDao = movieDao;
  }
}

Il faut indiquer la configuration :


<bean class="com.example.controller.MovieController">
  <property name="movieDao" ref="movieDao"></property>
</bean>
<!-- movieDao is defined elsewhere in the container configuration -->
<bean id="movieDao" class="com.example.dao.MovieDao" />

De la même façon, l’injection de l’entityManager via l’annotation @PersistenceContext doit être remplacé par l’appel à une classe statique.

source : https://developers.google.com/appengine/docs/java/datastore/jpa/overview?hl=fr#Getting_an_EntityManager_Instance

SGBD

L’infrastructure de GAE utilise une base de donnée NoSQL Big Table développée en interne par Google, ce qui peut impliquer la refonte de toute la couche de persistence et d’accès à la BDD.

source : http://blog.xebia.fr/2009/07/16/la-persistance-dans-google-app-engine-partie-une-le-datastore/
source : https://developers.google.com/appengine/docs/whatisgoogleappengine?hl=fr

ORM et persistence

Le SGBD utilisé par GAE impose le remplacement de Hibernate/JPA (relationnel) par Datanucleus/Objectify.

source : http://blog.publysher.nl/2012/02/improving-appengine-performance-from.html
source : http://turbomanage.wordpress.com/2010/01/28/simplify-with-objectify/

Transactions

L’application initiale se base sur l’annotation @Transactional de Spring AOP pour l’injection des dépendances.

Ce mécanisme ne semble pas compatible avec GAE, qui compte quelques bugs à ce sujet.

source : https://groups.google.com/forum/?fromgroups#!topic/google-appengine-java/68qhETz-UFU
source : http://objectuser.wordpress.com/2009/06/30/spring-jdo-in-google-app-engine/

Il faut donc complètement revoir cet aspect et préférer la manipulation explicite des transactions, via Objectify ou plus directement comme par exemple :


DatastoreService datastore = DatastoreServiceFactory.getDatastoreService()
try {
    Transaction txn = datastore.beginTransaction();
    // do stuff
    txn.commit();
}
finally {
    if (txn.isActive()) {
        txn.rollback();
    }
}

source : http://stackoverflow.com/questions/5386144/two-different-methods-for-google-app-engine-datastore-transactions-which-to-u
source : https://developers.google.com/appengine/docs/java/datastore/transactions

Logs

Puisque GAE utilise un JRE modifié ne contenant plus les librairies java.io.*, il n’est plus possible de se reposer sur Log4j ou Logback. Il n’y a plus d’appender à définir pour conserver la trace des logs, ils seront seulement disponibles dans la console d’administration de l’application.

Dans l'article suivant, je détaillerai la configuration nécessaire du POM projet pour réaliser une application GAE.


Fichier(s) joint(s) :



Xbee sans Arduino

Comme vous l'aurez remarqué dans mon article précédent, je me lance dans les montages électroniques, basés sur la platine Arduino et tout son écosystème.

Aujourd'hui je vous propose d'aller plus loin : nous allons voir comment établir une communication sans fil grâce aux modules radio Xbee. Le projet ici est de recevoir sur PC les mesures d'un capteur de température distant.

Matériel nécessaire


Module Xbee
Série 2 (x2)
Carte interface Adaptateur USB-miniUSB

Mon petit conseil pour l'achat de tout ceci est de commander sur le site Matlog : un revendeur français très bien équipé garantissant une livraison rapide.

Vous aurez également besoin du logiciel de configuration X-CTU fourni par Digi, fabricant des modules Xbee et d'un petit capteur de température (LM35 ici).

Principes

Les modules XBee permettent de créer des réseaux, du plus simple au plus complexe, Peer-to-peer ou maillé. Pour ce faire, ils peuvent être configurés pour jouer différents rôles : endpoint, routeur ou coordinateur (voici une bonne explication des différents rôles). Dans notre montage, nous allons avoir besoin d'un routeur (qui fera office d'endpoint) et d'un coordinateur.

Le montage

Avant de continuer, voici un aperçu du résultat :

Comme vous le voyez sur la première image, aucun micro-contrôleur n'est nécessaire. Nos deux modules radio communiquent directement et le routeur sera programmé pour envoyer à intervalles régulier la valeur du capteur de température. Le coordinateur est relié au PC directement via un câble USB et identifié par une petite pastille (par commodité). La seconde image montre en détail le montage : le fil blanc permet d'indiquer au module la valeur de référence du courant utilisé, afin qu'il puisse nous donner une valeur relative cohérente du signal reçu sur la broche 20 (première broche analogique du Xbee, fil rouge à droite). Le reste du câblage n'est que de l'alimentation par la pile 9V.

Configuration

L'étape la plus complexe. Nous allons voir comment attribuer chaque rôle aux modules et leur permettre de communiquer entre eux. Les XBee étant des modems radio, ils répondent à des commandes normalisées commençant par "AT". Je ne vais cependant pas m'étendre sur ce point dans cet article, mais plutôt utiliser un logiciel permettant de "masquer" ce niveau.

Commençons par le coordinateur. Une fois le premier module relié au PC, il faudra installer le driver, comme indiqué sur le site de Digi et lancer X-CTU :

Dans le permier onglet "PC Settings", il suffit de sélectionner la ligne correspondant au port COM sur lequel est branché notre XBee. Ensuite, dans l'onglet "Modem Configuration", choisir la configuration présentée : Modem XB24-ZB (pour les modules 2.4GHz Zigbee serie 2) et la dernière version du firmware. Cliquer ensuite sur Write pour envoyer cette première configuration au module.

Cliquer ensuite sur Read pour récupérer les valeurs par défaut définies par le module afin de pouvoir les ajuster à notre besoin. Pour faire simple, nous n'allons ici modifier que l'adresse du réseau (PAN ID ou ID) : n'importe quelle valeur aléatoire suffit. Cliquer de nouveau sur Write après modification pour mémoriser les changements.

Et voilà! Le point principal de notre réseau est configuré! Passons maintenant au routeur, qui va nous transmettre les informations du thermomètre. Même manipulation donc pour ce qui est du raccordement au PC et à la connexion dans X-CTU. Mais cette fois-ci, il faut indiquer la configuration suivante :

On affecte le même identifiant de réseau (ID) que le coordinateur (456) et les adresses de destination (DH et DL) correspondant à celles du coordinateur (0). La section "I/O settings" permet d'indiquer la gestion des entrées/sorties : ici nous choisissons de configurer le broche 20 (DI0) en entrée analogique (mode 2) ce qui donne DI02. Enfin, dans la sous-section "I/O sampling", nous définissons les intervalles de temps auxquels le routeur nous enverra des données : 12C en hexadécimal = 300 millisecondes.

Ensuite... Il ne reste plus qu'à rebrancher le tout! Tout devrait fonctionner. J'avoue tout de même qu'il m'a fallu pas mal d'heures de recherche pour réunir tous ces éléments. Si vous avez des soucis, n'hésitez pas à m'en parler en commentaire j'essaierai de vous aider.

Vous pouvez observer les données reçue via le Terminal de XCTU :

Je passe un peu sur l'analyse de la trame hexadécimale reçue (il y a des documents très complets sur le site de Digi à ce sujet) mais vous pouvez voir que la valeur entourée correspond à ce que nous envoie le capteur de température : D9 = 217. Selon la spécification de notre capteur LM35, pour trouver la valeur réelle de la température, il faut effectuer un calcul tout simple : 10mV par °C donc pour nous 217 / 10 = 21.7°C :D

Pour aller plus loin...

Ne nous arrêtons pas en si bon chemin! Comme vous l'avez vu, notre module capteur de température est autonome et alimenté par une pile. Même si les XBee sont assez peu énergivores, il faut tout de même rapidement penser à la gestion de l'énergie et à la réduction de leur consommation. Pour ce faire, ils disposent d'un mode veille qui augmente considérablement la durée d'utilisation des batteries.

Voici une brève description des différents modes :

  • ATSM 0 : Pas de veille
  • ATSM 1 : "pin wake mode" : la mise en veille ou le réveil sont gérées par l'alimentation ou l'extinction de la broche 9
  • ATSM 2 et 3 : réservés, non utilisables
  • ATSM 4 : Entrée et sortie de veille par intervalle de temps
  • ATSM 5 : combinaison des 1 et 4

A ceci nous allons ajouter un temps défini pendant lequel le module restera allumé avant de retourner en veille (afin de garantir la transmission des paquets).

Dans notre exemple, nous allons paramétrer une veille de 10 secondes et un temps d'attente de 1,5 seconde. Ce qui nous donne dans XCTU :

  • SM 4
  • SP 3E8 (= 1000 multiplié par 10 = 10 secondes)
  • ST 96 (= 150 multiplié par 10 = 1.5 seconde)

Et là... Il se peut que cela ne fonctionne pas! ;) En effet, à chaque sortie de veille, le routeur doit s'identifier de nouveau auprès du coordinateur (qui a considéré qu'au bout de quelques secondes de non réponse il ne faisait plus partie du réseau), ce qui prend normalement quelques secondes, impossible au vu de notre temps d'activité de 1.5 secondes. Pour y remédier, comme indiqué dans cet article, il faut modifier la configuration du coordinateur et augmenter les valeurs de SP et SN afin de couvrir les cycles de veille du routeur.

Cette fois notre matériel est prêt : nous recevons toutes les 10 secondes la température du module distant.

Et la cerise sur le gateau

Tout ceci est bien, mais il serait plus intéressant de recevoir ces informations dans un programme Java, afin de pouvoir s'en servir dans n'importe quel contexte. Pour cela, rien de plus simple : XBEE API. Quelques lignes de code plus tard :


import com.rapplogic.xbee.api.XBee;
import com.rapplogic.xbee.api.XBeeException;
import com.rapplogic.xbee.api.zigbee.ZNetRxIoSampleResponse;

public class TempRead {
 
 public static void main(String[] args) throws XBeeException {
  XBee xbee = new XBee();         
  xbee.open("COM6", 9600);
                          
  while (true) {
   ZNetRxIoSampleResponse ioSample = (ZNetRxIoSampleResponse) xbee.getResponse();
//      System.out.println("We received a sample from " + ioSample.getRemoteAddress16());     
      if (ioSample.containsAnalog()) {
          System.out.println("Temperature : " + ioSample.getAnalog0()/10.0);
      }
  }
 }

}

Ouf, enfin terminé! Avec cette API, nous sommes capables de savoir quel module nous a envoyé des données et de récupérer directement la valeur reçue sur la broche 20.

J'espère vous avoir donné assez d'éléments pour vous lancer de votre côté, bon courage!

Sources


Fichier(s) joint(s) :



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) :



Un moteur de recherche pour piles d'exceptions

Quoi de plus pénible que d'essayer de trouver dans les moteurs de recherche la cause d'une erreur à partir de la stracktrace??

Heureusement il existe Brainleg, un moteur de recherche spécialisé :

Il suffit de coller la pile d'erreur dans le champ de recherche pour que le moteur affiche une liste de sites dans lesquels cette pile (ou une pile similaire) a été postée.

Brainleg se comporte comme un méta-moteur qui indexe un grand nombre de sites de référence et de forums afin de retrouver les traces des erreurs :

Pour ce qui est de l'intégration dans les IDEs, Brainleg ne propose qu'un plugin IDEA. Pour Eclipse, il vaut mieux se tourner vers le plugin Code Recommenders qui propose une fonctionnalité similaire.


Fichier(s) joint(s) :



Détection de deadlock en Java

Un petit article afin de partager quelques liens très utiles vers des méthodes simples et efficaces pour observer et analyser des deadlocks.

Ces exemples étant déjà très complets et précis, je ne vais pas plagier, je vous laisse les découvrir par vous-mêmes :

Bonne lecture!


Fichier(s) joint(s) :



Eclipse RCP : exécuter une LaunchConfiguration

Dans une application RCP, il peut être utile de créer une action pour exécuter une launchConfiguration présente dans le workspace.

Pour ce faire, il va falloir utiliser des classes présentes dans le plugin org.eclipse.debug et re-créer manuellement la configuration telle que l'IDE l'exécute lorsque l'on lance l'action "Run...".

Commençons par exporter la configuration dans un fichier afin de voir de quoi elle est constituée. Dans le menu "Run configurations", ouvrir l'onglet Common pour définir l'endroit où sauvegarder le fichier (option "Shared file") :

A la fermeture de cette fenêtre, un fichier *.launch a été créé. Voici un exemple de contenu :


<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ant.AntLaunchConfigurationType">
<booleanAttribute key="org.eclipse.ant.ui.DEFAULT_VM_INSTALL" value="false"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/my.project/buildProduct.xml"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="1"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.ant.ui.AntClasspathProvider"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="my.project"/>
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.ant.ui.AntClasspathProvider"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/my.project/buildProduct.xml}"/>
<stringAttribute key="process_factory_id" value="org.eclipse.ant.ui.remoteAntProcessFactory"/>
</launchConfiguration>
 

Le fichier est finalement assez simple, on trouve le type de configuration (ici une tâche Ant) et tous les paramètres requis.

Et voici en quelques lignes comment créer les objets nécessaires pour le lancement manuel :


import java.util.Arrays;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.ui.DebugUITools;

public class RunAntBuild {

 public static void main(String... args) {
  ILaunchManager manager = DebugPlugin.getDefault().getLaunchManager();
  ILaunchConfigurationType type =
        manager.getLaunchConfigurationType("org.eclipse.ant.AntLaunchConfigurationType");
  try {
   ILaunchConfigurationWorkingCopy workingCopy =
           type.newInstance(null, "Run ant task");
   workingCopy.setAttribute("org.eclipse.ant.ui.DEFAULT_VM_INSTALL","false");
   workingCopy.setAttribute("org.eclipse.debug.core.MAPPED_RESOURCE_PATHS",Arrays.asList("/my.project/buildProduct.xml"));
   workingCopy.setAttribute("org.eclipse.debug.core.MAPPED_RESOURCE_TYPES",Arrays.asList("1"));
   workingCopy.setAttribute("org.eclipse.jdt.launching.CLASSPATH_PROVIDER","org.eclipse.ant.ui.AntClasspathProvider");
   workingCopy.setAttribute("org.eclipse.jdt.launching.PROJECT_ATTR","my.project");
   workingCopy.setAttribute("org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER","org.eclipse.ant.ui.AntClasspathProvider");
   workingCopy.setAttribute("org.eclipse.ui.externaltools.ATTR_LOCATION",antXmlPath);
   workingCopy.setAttribute("process_factory_id","org.eclipse.ant.ui.remoteAntProcessFactory");
   DebugUITools.launch(workingCopy, ILaunchManager.RUN_MODE);
  } catch (CoreException e) {
   e.printStackTrace();
  }
 }

}

Fichier(s) joint(s) :

Eclipse RCP : fenêtre de sélection de ressource

Petit mémo pour ne pas ré-inventer la roue : voici un lien très utile qui recense les fenêtres de dialogue facilement implémentables disponibles dans la plateforme RCP : EclipseTips - Selection Dialogs in Eclipse

Et un petit exemple, avec un bonus. Ici on crée une fenêtre permettant de sélectionner uniquement un fichier XML présent dans le workspace :


final ElementTreeSelectionDialog selectDialog = new ElementTreeSelectionDialog(Display.getDefault().getActiveShell(), 
  new WorkbenchLabelProvider(), new BaseWorkbenchContentProvider());
selectDialog.setTitle("Sélection de ressource");
selectDialog.setMessage("Sélectionner le fichier XML :");
selectDialog.setInput(ResourcesPlugin.getWorkspace().getRoot());
selectDialog.setAllowMultiple(false);
selectDialog.setHelpAvailable(false);
selectDialog.setValidator(new ISelectionStatusValidator() {
 @Override
 public IStatus validate(final Object[] arg0) {
  boolean isFileOK = arg0.length>0 && (arg0[0] instanceof IFile) && ((IFile)arg0[0]).getName().endsWith(".xml");
  selectDialog.getOkButton().setVisible(isFileOK);
  return isFileOK ? Status.OK_STATUS : Status.CANCEL_STATUS;
 }
});
selectDialog.open();

String xmlPath = ((IFile)selectDialog.getFirstResult()).getRawLocationURI().getPath().substring(1);

On notera l'utilisation du ISelectionStatusValidator pour valider l'objet sélectionné.

Hope this helps!


Fichier(s) joint(s) :

Eclipse RCP : intéragir avec une tache ANT

Dans cet article je vais décrire comment lancer, dans une application RCP, une tâche Ant et surtout comment intéragir avec : input, console...

Tout d'abord, voici le script Ant à manipuler :


<project default="main">
    <target name="deploy.checks">
        <input message="Etes vous sûrs de vouloir lancer ce script?"
         validargs="o,n" addproperty="do.answer.runtime" />
        <condition property="do.abort.runtime">
            <equals arg1="n" arg2="${do.answer.runtime}"/>
        </condition>
        <fail if="do.abort.runtime">Abandon.</fail>
 </target>
    <target name="main" depends="deploy.checks">
     <echo message="Run from Eclipse!"/>
    </target>
</project>

Comme on le voit, celui-ci est assez simple, avec une tâche de demande de saisie et une tâche principale affichant des informations. Pour continuer, notre outil va avoir besoin des librairies suivantes :

  • ant.jar
  • ant-launcher.jar

Et voici le code :


import java.io.File;
import java.io.PrintStream;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DefaultLogger;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectHelper;
import org.apache.tools.ant.input.DefaultInputHandler;
import org.apache.tools.ant.input.InputRequest;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.console.ConsolePlugin;
import org.eclipse.ui.console.IConsole;
import org.eclipse.ui.console.MessageConsole;

public class RunAntBuild {

 public static void build() {
  // Fichier ant
  File buildFile = new File("...");
  
  // projet ant
  Project p = new Project();
  
  // gestion des inputs ant
  p.setInputHandler(new PluginInputHandler());
  
  // definition du logger...
  DefaultLogger consoleLogger = new DefaultLogger();
  
  // ... qui sera une console
  MessageConsole capiBuildConsole = new MessageConsole("AntConsole", null);
  
  // on ajoute notre console ant aux consoles de l'IDE Eclipse
  ConsolePlugin.getDefault().getConsoleManager().addConsoles(new IConsole[] {
   capiBuildConsole
  });
  
  // le stream de sortie pour ant
  PrintStream printStream = new PrintStream(capiBuildConsole.newOutputStream());
  consoleLogger.setErrorPrintStream(printStream);
  consoleLogger.setOutputPrintStream(printStream);
  consoleLogger.setMessageOutputLevel(Project.MSG_INFO);
  p.addBuildListener(consoleLogger);
  
  // definition des propriétés
  p.setUserProperty("ant.file", buildFile.getAbsolutePath());
  
  p.init();
  ProjectHelper helper = ProjectHelper.getProjectHelper();
  p.addReference("ant.projectHelper", helper);
  helper.parse(p, buildFile);
  p.executeTarget(p.getDefaultTarget());

 }

 static class PluginInputHandler extends DefaultInputHandler {
  @Override
  public void handleInput(InputRequest arg0) throws BuildException {
   InputDialog dlg = new InputDialog(Display.getCurrent().getActiveShell(), "Vérification", arg0.getPrompt() + " (o/n)", "",
     null);
   if (dlg.open() == Window.OK) {
    arg0.setInput(dlg.getValue());
   }
  }
 }

}

Le code et les commentaires parlent d'eux-mêmes, mais il est intéressant de noter l'utilisation de la console via le ConsolePlugin et de l'InputDialog comme utilitaire pour permettre la saisie de paramètres d'entrée.

Voici donc le résultat en images :

Ouverture de la fenêtre d'input au début du script

Affichage dans la console des sorties texte du script

Et voilà! En espérant vous avoir fait gagner du temps...


Fichier(s) joint(s) :