Club robotique de Sophia-Antipolis

Home > POBOTpedia > Lego Mindstorms > Découverte des Lego Mindstorms EV3 > A first Java program for the EV3

A first Java program for the EV3

Tuesday 12 November 2013, by Eric P.


All the versions of this article: [English] [français]

This article describes how to setup the environment for developing Java programs for the EV3.

Credits

A noticeable part of the information included in this article comes from the leJOS for the EV3 project wiki and forum.

I have added some learnings from my first experiments, including details about an Ant based build chain and a small demonstration program.

The context

The experiments described here use a Linux Ubuntu 12.04 configuration. They should be applicable to Mac OSX and Windows systems, but I can’t guaranty.

So Mac OSX and Windows users could have to adpat a bit the build scripts. I just provide the basis here, and it will be up to you for completing the journey ;)

Development environment

We will use Eclipse, since we have to admit that it is a very comfortable environment for a quick start and is thus well suited for beginners. This being said, rather that considering only the plugins based approach, we will package the whole build chain, from compilation to runtime upload and execution, using an Ant script, which will be usable also from the command line for people preferring other development tools.

The best way for installing all we will need is to follow the instructions detailed on the excellent leJOS Wiki. All is said there, and you’ll have no problem at all if you carefully follow their steps. I’m not going to repeat them here, since it’s better to avoid wasting the planet energetic resources by copying 15 000 times the same texts on 15 000 different servers ;)

You’ll have to be very careful when selecting the EV3 sources version, since the ev3classes library and accompanying tools included in the leJOS is based on version 0.4.0. If you don’t pay attention, you’ll download the MASTER version of the Git repository, which is not compatible, because a lot or refactoring is under work. The net result will be that the test program included in the repository will not run on the EV3, because the differences at classes and packages level between MASTER and 0.4.0 versions.

If ever you ended up with the wrong version of the repository, this can be easily fixed by a right click on the ev3classes project in the Eclipse view, and then the execution of the command "Replace with.../Branch, Tag or Reference..." for selecting the 0.4.0 version tag.

Another (untested) solution could be to copy the MASTER version of ev3classes.jar on the EV3 (but not replacing the original one, since some tools use it) and put it in the class path when running the test program.

A different binary upload procedure

At this point, I’ll cease repeating what other people have already explained very well, and relate my own experiments.

The method described in the Wiki us based on the Eclipse "Export jar" command and then the "Remote System Explorer" plugin. This works very well, by this is far too many clicks and mouse moves to my taste. I prefer when all happens with a single action from me, to which I can attach a keyboard shortcut for avoiding endless keyboard-mouse back and forth moves.

This is where Ant comes to our rescue. For those who don’t know what Ant is, let’s say that it is for Java what make is for other compiled languages. Whats is make did you say ? Ahem, just ask Google ;)

We’ll define all the actions to be done in an Ant script, so that they can be triggered individually, by also as an automatic sequence, skipping steps that don’t need to be re-done (for instance compiling an already compiled source, which has not been changed). Just as make does. It must be noted that Eclipse embeds an Ant engine, so you’ll not need to install it explicitly on your system. Of course you’ll have to if you intend to run the Ant script form the command line.

Our script will define the following targets :

  • compile for compiling the sources (useful for executing the procedure outside Eclipse, since no automatic compilation will be done in this case)
  • dist for generating the jar to be uploaded on the EV3
  • upload for uploading it
  • run for starting the program execution from your development system, and without having to open a ssh or telnet terminal to connect to the EV3
  • clean to remove all the generated stuff as usual

The script is initialized by this stance :

<?xml version="1.0" encoding="UTF-8"?>
<project name="PobotSaysHello" default="dist" basedir=".">
<!-- more to come here later -->
</project>



Let’s now have a look at the details in it.

Preliminary definitions

A good habit is to group as properties values subject to customization (file and directory paths for instance). Even if a value has few chances to be modified, as soon as it is used in at least two places, it is wise to define it as a property.

Wo we’ll include the following property definitions at the top of the Ant script :

<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" />

compile

This target and its friends are quite classical. Don’t forget to include the right version of ev3classes.jar in the build path (as mentionned above) :

<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

This target builds the application jar, without resorting to the Export command and its collection of fill-in forms :

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



You’ll notice that we have defined that this target depends on the compile one, so that compile will be automatically executed if required when requesting the dist target to be built..

The fileset added to the jar task copies the non-Java files (aka resources) which could have be put in the src tree. You’ll have to adapt this clause if you use a different sources organization, for instance if storing resources in a sibling tree.

upload

This is a place where we will spare a lot of manual actions, compared to the copy/paste procedure described in the Wiki.This target is based on the scp Ant task, which requires the JSch Java library. You’ll thus need to download it from site Sourceforge and install with the other Java libraries.

Under Linux, the jar must be put in /usr/share/java/, then a symbolic link to it must be added into /usr/share/ant/lib so tat Ant will add it in its classpath. There is also an option for putting it somewhere under your home dir, but I don’t remember where. Google will be your friend ;)

Once this is done, here is what the upload target looks like:

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



Same remark as before with respect to the dependency between this target and the dist one.

Note: If your system has the scp command, you can do without this extension, and just use the built-in exec task for executing it, the same way you would have entered it on the command line. But then your script will be tied to your OS. The scp Ant task option has the advantage to be cross-platform.

run

Last but not least : remotely start the program on the EV3.

We will use the sshexec Ant task, kindly brought to us by Jsch together with the scp one :

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



You’ll notice once again that we have used the tasks dependency mechanism to provide automatic chaining.

As before, an other option would have been to used the exec task to execution jrun as a command passed to ssh. It was my first approach, but it is not cross-platform, just like the scp command usage.

clean

I’ve added it here to be exhaustive, but you could have written it by yourself :

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


That’s all folks

Once all this stuff has been stored as an Ant script (usually named build.xml), you just have to open it in the the Ant Eclipse view, so that all the targets are listed and can be executed them by double-clicking on them.

It is also possible to refine a Run configuration for your project so that it will execute Ant for the run target. Now when you’ll use the Run command of Eclipse, the project will be compiled, its jar created, then uploaded and the execution will be started. Just with a single keystroke.

Here is an usage screeshot :

Example de session Ant

with the result on the EV3 which appears after a (rather long) delay [1] :

POBOT says Hello sur l’EV3

Isn’t it nice and more efficient that the 25 clicks procedure plus ssh console ?

Ending words

Here is the small program used in the above demonstration. It displays our beloved logo on the EV3 LCD, waits for a press on one of the buttons while flashing the LEDs, and then kindly says goodbye.

It illustrates two possible ways of loading a picture with the EV3 format : from a generated class, or from a binary resource file, embedded in the application jar. For more on this, have a look at this article.

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);
    }
 
}



The image data are generated from a standard graphic file (PNG, BMP,...), thanks to a small home-made tool described in this article.


[1we have to admit that for the moment, starting a Java program on the EV3 takes a while

Any message or comments?

pre-moderation

Warning, your message will only be displayed after it has been checked and approved.

Who are you?

To show your avatar with your message, register it first on gravatar.com (free et painless) and don’t forget to indicate your Email addresse here.

Enter your comment here

This form accepts SPIP shortcuts {{bold}} {italic} -*list [text->url] <quote> <code> and HTML code <q> <del> <ins>. To create paragraphs, just leave empty lines.