Using leJOS with Android
Using leJOS with Android

leJOS can be used in Android applications (.apks) via the leJOS PC API that communicates using Bluetooth with either:

  • leJOS NXJ programs running on your NXT. [This requires the leJOS firmware to be on your NXT]
  • the LEGO Communications Protocol (LCP). [This DOES NOT require the leJOS firmware to be on your NXT, and works with the standard LEGO firmware]

This tutorial is not a complete explanation of how to create Android applications which can quickly become far more complicated than the normal difficulty of using leJOS. Three leJOS sample programs have been included in one Android .apk, and this tutorial will explain the most relevant aspects of getting them to work under Android. Please note: The robotics package is not supported.

Android specific questions should be directed to theAndroid Developer Mailing List, while leJOS specific ones can go to our forum

Prerequisites

Android SDK

To build .apks that run on Android or to upload the sample .apk to your Android device, you need to download the SDK for your OS and install it.

IDE Plugins

There is an Eclipse Plugin or a Netbeans Plugin to simplify developing and building .apks. The LeJOSDroid sample project was set up using the Eclipse Android Plugin and can be imported into Eclipse by selecting File --> Import --> General --> Existing Projects into Workspace and selecting the android/leJOS-Droid folder

To use leJOS in your own Android project within Eclipse (same principles apply for Netbeans), make sure:

  • the lejos pccomms.jar (.9 or later) is in your project/libs dir

  • the lejos pccomms.jar is in your build path

Sdcard Cache File

leJOS uses a nxj.cache file to record the MAC addresses of paired NXT devices. Running the sample LeJOSDroid.apk will set this up for you. To enable this otherwise, create a leJOS directory in the root dir of your Android's sdcard. Without this cache file, leJOS will iterate through the list of paired devices on your Android handset and connect to the first NXT it finds.

Device Setup

To develop with an Android device, which is necessary for anything involving a BlueTooth connection to the NXT, you will need to set up your Android device for debugging. The Android emulator can be used to develop the GUI of your application, but the emulator can not do bluetooth connections.

Logging can be seen in the Eclipse window or via a shell by using logcat via adb

Back to top

LeJOSDroid Samples

The LeJOSDroid.apk Android application includes three leJOS samples modified from /pcsamples that typically run on your PC or MAC:

  • TachoCount
  • BTSend
  • RCNavigationControl

The samples have been tailored for Android, and it is important to understand the modifications required by Android Application Fundamentals

The LeJOSDroid.apk can be installed with the adb tool in your-android-sdk-location/platform-tools/. Please uninstall the distributed LeJOSDroid.apk from your Android device (via Settings --> Applications --> Manage Applications) before reinstalling a new LeJOSDroid.apk or there will be an error about conflicting signatures. How to sign an Application.

TachoCount

TachoCount works with either LEGO or leJOS firmware via LCP. Nothing needs to be uploaded to or run on your brick.

BTSend

The BTSend example requires that samples/BTRecieve must be uploaded and running on a NXT with leJOS firmware. See the section on flashing the leJOS firmware in getting started for your OS, and compiling and running leJOS programs on the NXT.

RCNavigationControl

The BTSend example requires that samples/RCNavigator must be uploaded and running on a NXT with leJOS firmware. See the section on flashing the leJOS firmware in getting started for your OS, and compiling and running leJOS programs on the NXT.

Back to top

Application Fundamentals

Below are some Android application fundamental considerations:

Further details on the topics touched on below, as well as complete information on UI design and other topics are available from the Android Developer Site

Connection

You will need to pair with the NXT from your Android device before being able to connect.

leJOS connection is done via the standard lejos.pc.comm.NXTConnector method. leJOS will detect the Android environment and attempt to load lejos.pc.comm.NXTCommAndroid. NXTCommAndroid is not included in the pccomm.jar due to Android dependencies and must be included in the src of your project.

You'll notice the LeJOSDroid method is static and returns a connected NXTConnector. The reason is that the two examples which use this code (BTSend and TachoCount), call it from another thread that those examples create. The reasons will be discussed in the next section on threading.

Example -- LeJOSDroid connection method:

private final static String TAG = "LeJOSDroid";
<--- used to label the logging
      
 
public static NXTConnector connect(final CONN_TYPE connection_type) {
    <--- method to return connection
    Log.d(TAG, " about to add LEJOS listener ");
	
    NXTConnector conn = new NXTConnector();
    conn.setDebug(true);
    conn.addLogListener(new NXTCommLogListener() {
        public void logEvent(String arg0) {
            Log.e(TAG + " NXJ log:", arg0);
        }
    
        public void logEvent(Throwable arg0) {
            Log.e(TAG + " NXJ log:", arg0.getMessage(), arg0);
        }
    });
    
    switch (connection_type) {      <--- specifies the connection type i.e. LCP or packet
    case LEGO_LCP:
        conn.connectTo("btspp://NXT", NXTComm.LCP);
        break;
    case LEJOS_PACKET:
        conn.connectTo("btspp://");
        break;
    }
    
    return conn;
}

Back to top

Threading

Most work in Android must be done off the main UI thread, otherwise the user will see a ANR (Android Non-Response) message when there is no response to an input event (e.g. key press, screen touch) within 5 seconds.

For this reason, the BTSend and TachoCount samples are run in their own thread. In particular, BTSend has a while loop that could take more than 5 seconds. Similarly, TachoCount could run into ANR problems if it were run on the main UI thread and included a method that took longer than 5 seconds to execute which is possible using LCP as some methods such as Motor.rotate() don't return until after completing.

It should be noted that while the NXTCommAndroid class creates threads internally for IO (one to read incoming and one to write outgoing data via bluetooth), the NXTCommAndroid read()method will block until data is available. Also, the NXTCommAndroid open() methods used for connection creation will block until the connection is made and IO streams are available. Since lejos.pc.comm.NXTConnector.connectTo() methods ultimately call the NXTCommAndroid open() method that blocks, calls to open connections should be done off the main UI thread as done in the examples.

The RCNavigationControl example has a more complex UI which isn't easily combined with LeJOSDroid and so is run as a new activity. This new activity is, in fact now, the UI thread that creates other threads to handle the connection and reading which block. If an ANR message appears from work done on the main UI thread, that work too will need to be threaded. In actuality, the code to connect for RCNavigationControl works fine even if it is taken out of it's own thread because in all likelyhood the user won't be creating an input event (via a key press or screen touch for example) while waiting for a connection to be made.

Back to top

Internal Messaging

One implication of Android threading is that Message Handlers or BlockingQueues must be used to exchange data between threads as direct communication is not advised.

RCNavigationControl is extremely illustrative of this point. In the original pcsamples/RCNavigationControl, the RCNavComms class is created while passing in the RCNavigationControl instance in the constructor, and the instance is used to update the UI.

public class RCNavigationControl extends javax.swing.JFrame{  //example for PC or MAC
    ...
    private RCNavComms communicator = new RCNavComms(this);   
    <------ This (using an instance to update the UI thread) is dangerous in 
    Android with potential memory leaks and unpredictable behavior
}
public class RCNavComms { //example for PC or MAC
    RCNavigationControl control;

    public RCNavComms(RCNavigationControl control){
        this.control = control;
    }
  
    class Reader extends Thread{
        ...
        public void run(){
            ...
            control.showtRobotPosition(x, y, h);   
            <------ This (using an instance to update the UI thread) is dangerous in 
            Android with potential memory leaks and unpredictable behavior
        }
    }     
}

In comparision, the RCNavComms class for the Android example uses a Handler that was passed in through the constructor (instead of the RCNavigationControl instance).

public class RCNavComms{ //example for Android
    Handler mUIMessageHandler;

    public RCNavComms(Handler mUIMessageHandler){
        this.mUIMessageHandler=mUIMessageHandler;
    }
     
    public void sendPosToUIThread(float x, float y, float h) {
        float[] pos= {x,y,h};
        Bundle b = new Bundle();
        b.putFloatArray(RCNavigationControl.ROBOT_POS, pos);
        Message message_holder = new Message();
        message_holder.setData(b);
        mUIMessageHandler.sendMessage(message_holder);
    }
  
    class Reader extends Thread{
        ...
        public void run(){
            ...
            sendPosToUIThread(x, y, h);  <------ Sends a message via a Handler
        }
    }     
}
public class RCNavigationControl extends TabActivity{ //example for Android
    ...
    class UIMessageHandler extends Handler {
        float[] pos;

        @Override
        public void handleMessage(Message msg) {
            //Log.d(TAG, "handleMessage");
            switch (msg.what) {

                case LeJOSDroid.MESSAGE:
                    //Log.d(TAG, (String) msg.getData().get(LeJOSDroid.MESSAGE_CONTENT));
                    mMessage.setText((String) msg.getData().get(LeJOSDroid.MESSAGE_CONTENT));
                    break;

                default:
                    pos = msg.getData().getFloatArray(ROBOT_POS);
                    showtRobotPosition(pos[0], pos[1], pos[2]);
            }
        }
    }
}

Please see the documentation to learn more about Handlers. While the RCNavigationControl example does in fact work even without using a Handler and passing in an instance to use to update the UI, it's very true that Threads will run briefly as the UI thread if you try to update the UI by passing in an instance and modifying the View (as would be done by using the PC or MAC example). Views in Android are decidedly single threaded and sanity checks may not catch what you are doing, and thus lead to unpredictable behavior. Also, there is a risk of memory leaks.

Back to top

LifeCycle Basics

Many of the LeJOSDroid methods such as onCreate(Bundle b), onPause(), onResume() are part of the Android Activity lifecycle and should be understood before creating your own application.

For instance, some essential setup is actually done in RCNavigationControl in onResume() instead of onCreate(). This is to enable correct functioning when the application resumes from onPause().

    @Override
    protected void onResume() {
        super.onResume();
        mUIMessageHandler = new UIMessageHandler();
        communicator = new RCNavComms(mUIMessageHandler);
    }

Back to top