Club robotique de Sophia-Antipolis

Accueil > Robopedia > Constituants > Les cartes à micro-contrôleurs > Lego Mindstorms > Découverte des Lego Mindstorms EV3 > Premier programme Java sur EV3

Premier programme Java sur EV3

dimanche 3 novembre 2013, par Eric P.


Toutes les versions de cet article : [English] [français]

Cet article retrace la mise en place de l’environnement adapté au développement de programmes Java pour l’EV3.

 Crédits

Autrement dit : rendons à César ce qui est à César

Une bonne partie des informations présentes dans cet article ne sont pas de mon invention, et beaucoup sont issues du wiki et du forum du projet leJOS pour l’EV3.

J’y ai ajouté quelques compléments issus de mes premiers essais, les détails d’une chaîne de production basée sur Ant, ainsi qu’un petit programme de démo.

 Contexte

Les expérimentations décrites ici ont été faites sous une configuration Linux Ubuntu 12.04. Normalement, elles sont totalement transposables dans un environnement OSX ou Windows

Les utilisateurs de Windows de d’OSX devront peut-être adapter les build files par endroit. Je vous donne ici les informations de base, à vous d’assumer vos choix ;)

 Environnement de développement

Nous utiliserons Eclipse, car il faut reconnaître qu’il est pratique pour démarrer rapidement et être ainsi accessible aux novices. Ceci étant, plutôt que de laisser la partie belle aux plugins, nous allons packager la chaîne de compilation, génération de l’archive, upload et lancement via un fichier Ant, qu’il est possible de lancer directement depuis la ligne de commande pour ceux qui préfèrent d’autres outils d’écriture du code.

Pour l’installer, ou y ajouter ce dont nous aurons besoin, le mieux est de suivre les instructions de l’excellent Wiki de leJOS. Tout y est dit et ça marche directement si on suit les instructions fournies. Je ne vais pas les répéter ici, car ayons pitié des ressources énergétiques de la planète et évitons de copier 15 000 fois les mêmes textes sur 15 000 serveurs différents.

Le seul petit glitch que j’ai rencontré en compilant le programme de test pour contrôler l’opération, est que la version des sources de la bibliothèque ev3classes utilisée est la dernière du Git si on n’y prend pas garde. Or leJOS pour EV3 est en développement intensif et les choses bougent beaucoup. L’erreur observée est la conséquence d’une réorganisation de sources tout ce qu’il y a de plus justifiée eu égard à la compatibilité de leJOS avec le RCX et le NXT également, mais incompatible avec la version du jar installée dans l’image du file system leJOS.

Ca se traduit par un message d’erreur à l’exécution sur l’EV3, causée par l’absence de certaines classes. La correction (qu’on trouve rapidement via Google) est de caler la version du projet ev3classes sous Eclipse sur celle qui est distribuée dans l’image ; soit la 0.4.0. Pour ce faire, il suffit d’un clic droit sur le projet ev3classes, puis une sélection de la commande "Replace with.../Branch, Tag or Reference..." de manière à se lier au tag 0.4.0.

Une autre solution (non testée) serait de récupérer le fichier ev3classes.jar depuis l’image du file system leJOS, de le copier en local et de le référencer dans le build path de votre projet au lieu d’y référencer le projet ev3classes. Je laisse au lecteur assidu le soin de faire l’exercice et de nous informer si ça marche comme prévu ou non.

 Alternative à la procédure de transfert et lancement sur l’EV3

C’est ici que je vais cesser de raconter ce que les autres ont proposé, et vous faire part de mes propres expérimentations.

L’approche décrite dans le Wiki concernant la production du jar, son transfert sur l’EV3 et son lancement est basée sur la commande "Export" et sur le "Remote System Explorer" d’Eclipse. Ca fonctionne très bien, mais je lui reproche de nécessiter trop de clics à droite et à gauche pour arriver à ses fins. J’aime bien que tout se passe via une seule action de ma part, à laquelle je puisse attacher un raccourci clavier pour ne pas être obligé de faire un incessant aller-retour clavier-souris.

C’est à ce niveau que Ant peut venir à notre secours. Pour ceux qui ne connaissent pas, Ant est à Java ce que Make est au reste des développeurs.

Nous allons donc définir toutes les actions à réaliser dans un script Ant, qui permettra de les déclencher individuellement mais aussi de les enchaîner automatiquement, en ne répétant pas celles qui n’ont pas besoin de l’être (tout comme Make le fait). A noter que le script peut être lancé sous Eclipse même si Ant n’est pas installé sur votre système, car Eclipse embarque sa propre version de Ant sous forme de plugin lorsqu’on y installe les outils Java (ou qu’on a installé la version packagée pour Java). Il est évident qu’il devra l’être si vous voulez le lancer depuis la ligne de commande.

Notre script va définir les cibles suivantes :

  • compile pour compiler les sources (utile pour lancer la procédure hors d’Eclipse)
  • dist pour générer l’archive jar à transférer sur l’EV3
  • upload pour effectuer le transfert
  • run pour lancer l’exécution du programme à distance, sans devoir ouvrir un terminal en ssh sur l’EV3
  • clean pour faire le ménage comme d’habitude

On initialise le script avec ce qui suit :

<?xml version="1.0" encoding="UTF-8"?>
<project name="PobotSaysHello" default="dist" basedir=".">
<!-- le reste viendra ici -->
</project>



Voyons maintenant son contenu en détail.

Définitions préalables

Il est toujours plus sage de regrouper sous forme de properties les valeurs susceptibles d’être modifiées. Même si elle a peu de chances de l’être, dès qu’une valeur est présente à plusieurs endroits (comme des répertoires par exemple), il est de bon ton de prendre cette habitude.

Nous définissons donc en tête de notre script Ant les properties suivantes :

<property name="jar" value="${ant.project.name}.jar" />
<!-- change the main class name if not equal to the project name -->
<property name="main-class" value="${ant.project.name}" />
<property name="ev3.ipaddr" value="10.0.1.1" />
<property name="ev3.user" value="root" />
<property name="ev3.password" value="" />
<!--  change the value here by the path of the directory where ev3classes.jar is -->
<property name="ev3classes.path" value="/home/eric/git/ev3/ev3classes" />
<property name="src" value="src" />
<property name="build" value="bin" />
<property name="dist" value="dist" />

Note : je commente toujours mes sources en Anglais. Vous devez commencer à être habitués ;)

compile

La cible de compilation et ses annexes sont classiques, en n’oubliant pas d’inclure dans le build path le jar de la librairie ev3classes à la bonne version (cf plus haut) :

<path id="compile.classpath">
    <fileset dir="${ev3classes.path}">
        <include name="**/*.jar" />
    </fileset>
</path>
   
<target name="init">
    <!-- Create the time stamp -->
    <tstamp />
    <mkdir dir="${build}" />
</target>
   
<target name="compile" depends="init" description="compile the sources">
    <!-- Compile the java code from ${src} into ${build} -->
    <javac srcdir="${src}" destdir="${build}" includeantruntime="false">
        <classpath refid="compile.classpath" />
    </javac>
</target>



dist

Cette cible construit le jar pour l’EV3, sans devoir aller chercher la commande Export et sa collection de formulaires :

<target name="dist" depends="compile" description="Create the jar for the EV3">
    <mkdir dir="${dist}" />
    <jar jarfile="${dist}/${jar}" includes="*.class" basedir="${build}">
        <fileset dir="${src}" excludes="**/*.java"/>
    </jar>
</target>



On notera qu’elle définit une dépendance sur la cible compile, ce qui permet d’enchaîner automatiquement les opérations si nécessaire lorsqu’on demande directement l’exécution de la cible dist.

Le fileset ajouté à la tâche jar permet de copier également les fichiers non-Java (ressources) qui ont été placés dans l’arborescence src. Cette clause est à adapter si vous utilisez une organisation différente de vos projets, en plaçant les ressources dans une arborescence séparée par exemple.

upload

C’est ici qu’on gagne énormément de temps par rapport à la procédure "manuelle" décrite sur le Wiki leJOS. Cette cible est basée sur la task scp de Ant, qui utilise la librairie Java JSch. Il vous faudra donc télécharger celle-ci depuis son site Sourceforge.

Sous Linux, le jar est à placer dans /usr/share/java/, puis un lien symbolique pointant dessus est à définir dans le répertoire /usr/share/ant/lib pour que Ant l’insère dans son classpath. On peut aussi procéder en plaçant la lib sous le home dir de l’utilisateur, dans le répertoire $HOME/.ant/lib. De toute manière, à moins que vous ne soyez administrateur d’une machine utilisée par plusieurs personnes, où que vous n’ayez pas les droits administrateur sur la machine que vous utilisez, autant ne pas se faire des noeuds au cerveau. Dans tous les cas, une rapide recherche concernant l’installation d’extensions Ant vous donnera l’information. Il faut bien qu’il vous reste un peu de travail à faire ;)


Important :

Il y a un "petit détail" à ne pas oublier, et qui vous évitera des migraines pour résoudre une obstination d’Ant à se plaindre de l’absence d’une de ses dépendances lorsque vous l’utilisez sous Eclipse, alors que ça fonctionne très bien en ligne de commande. La première chose à réaliser est que le Ant lancé sous Eclipse n’est pas le Ant ligne de commande, comme déjà évoqué. Eclipse embarque son propre Ant (en fait ce sont les mêmes lib derrière, mais installées de manière totalement distinctes). Du coup ce n’est pas parce que l’un fonctionne que l’autre en fait autant. Lorqu’on ajoute manuellement une extension Ant, il faut aussi ajouter son jar dans le classpath de la version embarquée dans Eclipse. Pour cela, ouvrir le dialogue des Préférences d’Eclipse, puis la page Ant/Runtime et ajoutez le jar (Add External Jars...) dans l’onglet Classpath, sous la racine Ant Home Entries.

Ceci étant dit, voici à quoi ressemble la cible upload :

<target name="upload" depends="dist" description="Upload the jar to the EV3">
    <scp file="${dist}/${jar}" todir="${ev3.user}@${ev3.ipaddr}:" password="${ev3.password}" />
</target>



Même remarque que précédemment concernant la dépendance de cette cible sur une de ses copines.

Remarque : Si vous disposez de la commande scp sur votre système, vous pouvez vous passer de cette extension en utilisant la tâche exec permettant d’exécuter n’importe quelle commande depuis votre script Ant. Cependant, la méthode décrite ici a l’avantage de ne pas dépendre de prérequis système et est donc plus cross-plateforme.

run

Le fin du fin : lancer le programme EV3 à distance, depuis la machine de développement.

Nous allons exploiter ici la tâche Ant sshexec rendue disponible par l’installation de Jsch faite juste avant :

<target name="run" depends="upload">
    <sshexec
       host="${ev3.ipaddr}"
       username="${ev3.user}"
       password="${ev3.password}"
       command="jrun -cp ${jar} ${main-class}"
       />
</target>



Vous remarquerez qu’ici encore nous utilisons une dépendance pour enchaîner automatiquement les opérations, tout en n’exécutant que celles qui ont besoin de l’être.

Comme dans le cas précédent, nous aurions pu nous en sortir avec la tâche exec en lui faisant exécuter la commande jrun via ssh. C’était d’ailleurs le cas dans la première version de mon script.

clean

Je vous l’inclus pour être complet, mais vous auriez pu la coder comme des grands :

<target name="clean" description="clean up">
    <!-- Delete the ${build} and ${dist} directory trees -->
    <delete dir="${build}" />
    <delete dir="${dist}" />
</target>


E finita la comedia

On met tout cela ensemble, on demande à Eclipse de le charger dans la view "Ant" et il ne rester plus qu’à désigner la cible à exécuter (en l’occurrence "run") pour tester le code.

Mieux encore : vous pouvez très bien définir une configuration de Run pour le projet qui va faire cela pour vous. Du coup, lorsque vous utilisez la commande Run d’Eclipse sur votre projet, il va être mis à jour, le jar va être transféré sur l’EV3, et son exécution va être lancée dans la foulée.

En pratique, voici un exemple d’utilisation :

PNG - 61.2 ko
Example de session Ant

avec le résultat sur l’EV3, qui apparaît un peu après [1] l’affichage du message [sshexec] classpath : PobotSaysHello.jar : ... qui est produit par le script jrun :

JPEG - 258 ko
POBOT says Hello sur l’EV3

Ce n’est pas plus efficace que la procédure à 25 clics plus la console ssh sur l’EV3 ?

 Et pour finir

Voici le petit programme à la sauce POBOT utilisé dans la démo qui précède. Il affiche notre logo préféré sur l’écran de l’EV3, attend une action sur un des boutons tout en faisant flasher les LEDs pour nous le faire savoir, et dit gentiment au revoir ensuite.

Il illustre deux modes de chargement d’une image au format EV3 : depuis une classe générée, ou depuis un fichier ressource binaire. Pour plus de détails, reportez-vous à l’article sur l’outil de génération d’images au format EV3.

import java.io.DataInputStream;
import java.io.IOException;
 
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
 
import lejos.nxt.Button;
import lejos.nxt.LCD;
 
public class PobotSaysHello {
    static int SW = LCD.SCREEN_WIDTH;
    static int SH = LCD.SCREEN_HEIGHT;
 
    Graphics   g  = new Graphics();
 
//    Image getImageFromSource() {
//        return LogoPobot.image;
//    }
 
    Image getImageFromResource() {
        ClassLoader cl = this.getClass().getClassLoader();
        DataInputStream is = new DataInputStream(cl.getResourceAsStream("res/logo.bin"));
        try {
            int w = is.readShort();
            int h = is.readShort();
            int lg = is.readInt();
            byte[] bytes = new byte[lg];
            is.readFully(bytes);
            return new Image(w, h, bytes);
             
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
 
    void displayLogo() {
//        Image logo = getImageFromSource();
        Image logo = getImageFromResource();
        if (logo != null)
            g.drawRegion(
                logo, 0, 0, logo.getWidth(), logo.getHeight(),
                0,
                SW / 2, SH / 2,
                Graphics.HCENTER | Graphics.VCENTER
                );
    }
 
    void sayGoodBye() {
        g.clear();
 
        g.setFont(Font.getDefaultFont());
        g.drawString("Bye bye", SW / 2, SH / 2, Graphics.BASELINE | Graphics.HCENTER);
 
        LCD.refresh();
    }
 
    public static void main(String[] args) {
        PobotSaysHello demo = new PobotSaysHello();
 
        demo.displayLogo();
        lejos.nxt.Button.LEDPattern(9);
        Button.waitForAnyPress();
 
        demo.sayGoodBye();
 
        lejos.nxt.Button.LEDPattern(1);
    }
 
}



Les données de l’image sont produites à partir d’un fichier graphique standard (PNG, BMP,...), au moyen d’un petit outil maison, qui est décrit dans cet autre article.


[1euphémisme, car il faut reconnaître que les applis Java mettent plusieurs secondes à démarrer

Un message, un commentaire ?

modération a priori

Attention, votre message n’apparaîtra qu’après avoir été relu et approuvé.

Qui êtes-vous ?

Pour afficher votre trombine avec votre message, enregistrez-la d’abord sur gravatar.com (gratuit et indolore) et n’oubliez pas d’indiquer votre adresse e-mail ici.

Ajoutez votre commentaire ici
  • Ce formulaire accepte les raccourcis SPIP [->url] {{gras}} {italique} <quote> <code> et le code HTML <q> <del> <ins>. Pour créer des paragraphes, laissez simplement des lignes vides.

Ajouter un document