Seiten

Montag, 27. Januar 2014

Lego Mindstorms EV3 Components: Infrared Sensor - Part 1

Now that we have demonstrated how to work with motors, the next component we will focus on is a sensor - the infrared sensor.

In this first part we will show how to use the ability of the sensor to find objects and recognize the distance between that object and the sensor. There are other functionalities like receiving commands from the infrared remote control, which we will focus on in later posts.

The used lejos version is 0.5.0-alpha like in our last postings as it is still the most current version (01/27/2014).

The robot

First, as always, we needed a new robot which uses the infrared sensor. Therefore I just enhanced the robot from the last episode a bit (so if you want to understand the source code in this posting it will help if you already have checked the two postings about using motors). I strengthened the basement between the two wheels, so that it wouldn't break because of the weight of the brick and other components. The brick is now no longer located directly between the wheels, but a bit more above. Additionally, I mounted the infrared sensor centered above / in front of the brick and plugged it into the sensor plug 2. The result looks like the following.



( could be a relative of disney's wall-e )

The parcours

As said, in this tutorial we will focus on the ability of the infrared sensor to locate objects and return the objects' distance.

The scenario I created for that purpose:
We have a walled zone (cage). The robot is placed anywhere in that cage and the robot will always move forwards until he is directly in front of a wall. He then will turn left (90°) and will again move forwards until he reaches the next wall.



The infrared sensor API

So, before looking at the code let's talk a bit about what classes and methods the api offers for the infrared sensor.

It's quite easy to use an infrared sensor. You can simply instantiate an object of the EV3InfraredSensor-class. The (slightly) harder part is to tell the program to use the functionality of the sensor to detect objects and return their distances - and react on the returned values of course. Therefore you need: RangeFinderAdaptor, RangeFeatureDetector and FeatureListener.

But it's not as hard as it might seem now.
The aim is to have a listener on the sensor, so that the it returns the distance of a recognized object in regular time intervals (let's say: return the object's distance every second).
We need a class, which implements the FeatureListener, to do that. In my opinion the name FeatureListener is a bit confusing.. "Feature" means the object that has been detected by the sensor. The class which implements it has to implement the method
public void featureDetected(final Feature feature, final FeatureDetector detector);
And this method is called in the given interval, so we react to the distance of the detected object here (we will see how that works in the code for the parcours).
But that implementing class is the last thing we will do.
The regular order for realizing our aim with the instantiated Sensor-object is:
  1. Instantiate a RangeFinderAdaptor-object and tell it in which mode the sensor is used
  2. Instantiate a RangeFeatureDetector-object and tell it: the interval of checking the object's distance and the maximum distance of objects. It also needs the (prior to this step) created RangeFinderAdaptor-object.
  3. Instantiate an object of the class which implements the FeatureListener
  4. Add the instantiated listener from step 3 to the RangeFeatureDetector-object from step 2.
That's it. So after the theory let's have a look at the finished plain code.

The code

We have two classes this time. The Main-Class (as always) and the class which implements the needed FeatureListener.

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.RangeFinderAdaptor;
import lejos.robotics.navigation.DifferentialPilot;
import lejos.robotics.objectdetection.FeatureListener;
import lejos.robotics.objectdetection.RangeFeatureDetector;
import lejos.utility.Delay;

public class Main {

    protected final static double NINETY_DEGREES = 90.0;
    protected final static double PILOT_SPEED = 50.0;
    protected final static int PILOT_ACCELERATION = 25;
    protected final static float MAX_DISTANCE = 100.0f;
    protected final static int INTERVAL = 500;
    protected final static double WHEEL_DIAMETER = 30.0f;
    protected final static double DISTANCE_BETWEEN_WHEELS = 170.0;

    public static void main(String[] args) {

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

        configurePilot(pilot);
        configureInfraredSensor(infraredSensor, pilot);

        //wait for the sensor to be completely initialized and start the robot
        Delay.msDelay(5000);
        System.out.println("    Starting!");
        pilot.forward();
        Button.waitForAnyPress();
    }

    private static void configureInfraredSensor(final EV3IRSensor infraredSensor, final DifferentialPilot pilot) {
        final RangeFinderAdaptor rangeFinderAdaptor = new RangeFinderAdaptor(infraredSensor.getDistanceMode());
        final RangeFeatureDetector rangeFeatureDetector = new RangeFeatureDetector(rangeFinderAdaptor, MAX_DISTANCE, INTERVAL);
        final FeatureListener detectedObjectListener = new DetectedObjectListener(pilot);
        rangeFeatureDetector.addListener(detectedObjectListener);
    }

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

The main-method is quite short and simple. In the first part we instantiate the pilot to control the wheels/motors and the infrared sensor. For more information on the pilot check the corresponding postings about using motors with the lejos api if you haven't done yet).
The EV3InfraredSensor-class only needs to know which plug it is plugged into to create a corresponding object.

After creating our needed pilot and sensor we need to configure them. Have a look at the configureInfraredSensor-method. That's were we realize the things described in the last paragraph.
  1. We create a RangeFinderAdaptor-object by telling the constructor in which mode the sensor should work.
  2. We create a RangeFeatureDetector and assign the RangeFinderAdaptor-object plus the maximum distance of objects and the interval where the sensor should check for objects.
  3. We create a FeatureListener-object which needs the pilot because we will control the pilot in it. We will see the implementation of the listener next.
  4. We assign the created listener to the RangeFeatureDetector so the featureDetected-method of the listener is called in the configured interval.
After configuring the sensor I set a delay of five seconds because the configuration needs some time. When I tried starting the pilot without a delay it came to weird reactions like the robot moving forwards and backwards some millimeters jerkily.
After the delay the robot will start moving forwards and the rest of the code is done in the listener, so let's have a look at it next. (Button.waitForAnyPress() is just used to let the program continue. Without that line the program would just end before the robot even started moving.)

The Listener-class:

import lejos.robotics.navigation.DifferentialPilot;
import lejos.robotics.objectdetection.Feature;
import lejos.robotics.objectdetection.FeatureDetector;
import lejos.robotics.objectdetection.FeatureListener;
import static org.rapidpm.ev3.infrared.Main.*;

public class DetectedObjectListener implements FeatureListener {

    private DifferentialPilot pilot;

    public DetectedObjectListener(final DifferentialPilot pilot) {
        this.pilot = pilot;
    }

    @Override
    public void featureDetected(final Feature feature, final FeatureDetector detector) {
        int range = (int)feature.getRangeReading().getRange();
        if(range <= 10){
            if(range <=2){
                System.exit(0);
            }
            pilot.stop();
            pilot.rotate(NINETY_DEGREES);
            pilot.forward();
        }
    }
}

We have a field for the pilot from the constructor which we will use in the featureDetected()-method which is called in the configured interval.
In that method we get the distance/range of a recognized object from the Feature-object (which represents the detected object).
If the range is greater than 10 (centimeters), nothing will happen. The robot will of course continue moving forward (remember that in the main-method we called pilot.forward() and told the program to wait for a button press to exit the program).
If the range is lower than 10, we know that the robot has reached a wall. So we tell the pilot to stop. After that we let the robot rotate by 90°. At last we tell the pilot to move forwards again. An exception to that behavior is the special case if the range is lower or equals 2 cm. If that is the case the program will exit. Why is that? Without that backdoor, the program would run until any button of the brick is pressed (remember the main-method). But instead of having to grab the robot and press a button we now can just hold any object directly in front of the infrared sensor and the program will exit.

The result

At the end I added some console output to the featureDetected-method to have some feedback during the execution of the program.

    @Override
    public void featureDetected(final Feature feature, final FeatureDetector detector) {
        int range = (int)feature.getRangeReading().getRange();
        if(range <= 10){
            if(range <=3){
                System.out.println("Recognized signal directly on front of me: exiting!");
                System.exit(0);
            }
            System.out.print("Reached wall! Stopping...");
            pilot.stop();
            System.out.println("done!");
            System.out.print("Rotating 90 degrees...");
            pilot.rotate(NINETY_DEGREES);
            System.out.println("done!");
            pilot.forward();
        } else {
            System.out.println("range: "+range);
        }
    }

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