Seiten

Montag, 10. Februar 2014

Lego Mindstorms EV3 Components: Infrared Sensor - Part 2

In this second part on the mindstorm's infrared sensor we will focus on the remote control and the ability of the remote sensor to recognize commands from that remote control respectively. There are multiple buttons on that remote control and we will control the robot by pressing different buttons.

The robot

We will use the robot from the first part as it already uses the infrared sensor. The mindstorm's remote control looks like the following:


There are two buttons on the left (red) and two buttons on the right (blue). There is a fifth button at the top center which is (in contrast to all other buttons) a toggling button. So if you press it one time, the remote control sends a signal continuously until the button is pressed a second time. Before taking the photo I pressed that button one time to activate the green signal led. We will use that toggle button on the next part of the infrared sensor articles.
The red slider at the bottom center is for selecting a frequency channel. You can select four channels and the current channel is shown in the red circle under the toggling button.

The parcours

There is the robot and there is the remote control. The aim is to press different buttons on the remote control to control and move the robot. We will use the two buttons on the left to let the robot rotate left/right, respectively the two buttons on the right to let the robot move forwards/backwards. When more than one button is pressed, the program should exit.


The infrared sensor API

There are two relevant modes for the infrared sensor. The distance mode and the seek mode. We used the former implicitly in the last article and we will use it in this one because it offers the possibility to receive commands from the infrared remote control. The latter can be used to receive the position and the distance of the remote control to the robot (the next tutorial will use the seek mode).

It's very easy to receive a command from the remote control. The only method we will use is infraredSensor.getRemoteCommand(0);
This will return an integer value which represents the pressed button on the remote control (e.g. the left upper button on the remote control will return 1). The parameter of the method is also an integer value which represents the frequency channel which we use on the remote control. You can select one of four different channels (via the red slider on the remote control). In this case we use channel 0 (which is called channel 1 on the remote control).

That's it. We don't need RangeFinderAdaptors, RangeFeatureDetectors or anything like that this time. So let's directly jump to the finished code and see how it works.

The code

There are two classes. The Main-class and an InfraredSignalCheckerThread, which extends Thread and contains the logic for received commands from the remote control.

Let's start with the Main-Class:

import lejos.hardware.Button;
import lejos.hardware.motor.Motor;
import lejos.hardware.port.SensorPort;
import lejos.hardware.sensor.EV3IRSensor;
import lejos.robotics.navigation.DifferentialPilot;

public class Main {

    protected final static double NINETY_DEGREES = 90.0;
    protected final static double PILOT_SPEED = 100.0;
    protected final static int PILOT_ACCELERATION = 500;
    protected final static double WHEEL_DIAMETER = 30.0f;
    protected final static double DISTANCE_BETWEEN_WHEELS = 170.0;

    public static void main(String[] args) throws InterruptedException {

        final DifferentialPilot pilot = new DifferentialPilot(WHEEL_DIAMETER, DISTANCE_BETWEEN_WHEELS, Motor.C, Motor.B);
        final EV3IRSensor infraredSensor = new EV3IRSensor(SensorPort.S2);
        final InfraredSignalCheckerThread checkerThread = new InfraredSignalCheckerThread(infraredSensor, pilot);

        configurePilot(pilot);
        checkerThread.start();
        Button.waitForAnyPress();
    }

    private static void configurePilot(final DifferentialPilot pilot) {
        pilot.setAcceleration(PILOT_ACCELERATION);
        pilot.setRotateSpeed(PILOT_SPEED);
        pilot.setTravelSpeed(PILOT_SPEED);
    }
}

If you've (hopefully) read our last article on the infrared sensor, you will recognize that the Main-class became a bit smaller this time. We don't have to configure anything on the infrared sensor.
First we create instances for the pilot and the infrared sensor again. The new thing is the InfraredSignalCheckerThread which extends Thread. This class contains all the logic for received commands. The logic is implemented in a Thread-extending class because we want to listen continuously for any received commands. The created instance of the InfraredSignalCheckerThread needs the infraredSensor-object of course and it needs the pilot (because we will control/move the robot corresponding to the pressed button on the remote control).

After we created the instances we configure the pilot and start the checkerThread. That's it. Let's have a look at the InfraredSignalCheckerThread-class now.

The InfraredSignalCheckerThread-class:

import lejos.hardware.sensor.EV3IRSensor;
import lejos.robotics.navigation.DifferentialPilot;

public class InfraredSignalCheckerThread extends Thread {

    private EV3IRSensor infraredSensor;
    private DifferentialPilot pilot;

    public InfraredSignalCheckerThread(final EV3IRSensor infraredSensor, final DifferentialPilot pilot){
        this.infraredSensor = infraredSensor;
        this.pilot = pilot;
    }

    @Override
    public void run() {
        while(true){
            final int remoteCommand = infraredSensor.getRemoteCommand(0);
            switch (remoteCommand){
                case 0:
                    pilot.quickStop();
                    break;
                case 1:
                    pilot.rotateLeft();
                    break;
                case 2:
                    pilot.rotateRight();
                    break;
                case 3:
                    pilot.forward();
                    break;
                case 4:
                    pilot.backward();
                    break;
                default:
                    System.out.println("button combination pressed..exiting...");
                    System.exit(0);
            }
        }
    }
}

Let's have a look at the run-method. As you can see we have an infinite loop. In that loop we get an integer command from the method infraredSensor.getRemoteCommand(0). The given parameter (0) means that the infrared sensor should listen at frequency channel 0 (remember the red slider on the remote control for selecting a channel).
So now the thread is checking for any commands on channel 0 continuously. As next we check which command was sent. The different buttons send different integer values. Here is the documentation from within the EV3IRSensor-class:

/** The button values are:
* 1 TOP-LEFT
* 2 BOTTOM-LEFT
* 3 TOP-RIGHT
* 4 BOTTOM-RIGHT
* 5 TOP-LEFT + TOP-RIGHT
* 6 TOP-LEFT + BOTTOM-RIGHT
* 7 BOTTOM-LEFT + TOP-RIGHT
* 8 BOTTOM-LEFT + BOTTOM-RIGHT
* 9 CENTRE/BEACON
* 10 BOTTOM-LEFT + TOP-LEFT
* 11 TOP-RIGHT + BOTTOM-RIGHT
* (0 means no buttons pressed)
*/

So, as you can see if no button is pressed on the remote control the method will return 0. The four buttons we want to use return 1, 2, 3 and 4.
Knowing these return values the switch statement becomes very easy to understand. If no button is pressed the robot shouldn't move. If the top left button is pressed the robot should rotate to the left and so on, we just call the corresponding methods of the DifferentialPilot-class to control the motors. If more than one button is pressed at a time the program should exit.

The result

And here is the result (watch on youtube for better quality):



Some words on complications that I had while writing the program: After starting the CheckerThread I had to wait around 30 seconds before pressing a button on the remote control. I have no explaination for that but when I pressed any button earlier the robot moved very jerkily. I tried different workarounds like Thread.sleep() and Delay.ms() in the main-method and the checker-thread. I also tried a timer in the CheckerThread which printed a message on the console to tell the user that the program isn't yet ready if he tried to press any button during those first 30 seconds. But none of these attempts worked.