Technoramic 5190

Writing a Driver-Controlled OpMode

Important Documentation:

Import Statements

There are a lot of things you’ll probably want to import, but you’ll almost always want some of these:

    import com.qualcomm.robotcore.eventloop.opmode.Disabled;  
    import com.qualcomm.robotcore.eventloop.opmode.OpMode;
    import com.qualcomm.robotcore.eventloop.opmode.TeleOp;  
    import com.qualcomm.robotcore.hardware.DcMotor;
    import com.qualcomm.robotcore.hardware.CRServo;
    import com.qualcomm.robotcore.hardware.Servo;
    import com.qualcomm.robotcore.hardware.DcMotorSimple;
    import com.qualcomm.robotcore.hardware.HardwareMap;
    import com.qualcomm.robotcore.util.ElapsedTime;

Android Studio will also usually prompt you if something’s not imported.

Making the Class

For Teleop, you start with an Annotation to tell the app to show the OpMode on the DriverStation on the TeleOp side:

@TeleOp(name="<What you want it to show as on the app>", group="<The group you want it in (useful for sorting and grouping)>")

You can add @Disabled if you want it to not show up in the app. Then you start it off with your actual class declaration:

public class driveTest extends OpMode { ... }

You could also just copy the one of the template OpModes from the sample OpModes - I use BasicOpMode_Iterative.

Declaring Hardware

To use motors, we need to somehow find a way to send voltages to the Core Power Distribution Module… luckily, FIRST’s whole app comes with a lot of tools that we just imported to help us do that. All we need to do is declare the objects that we want to use; their objects do a lot of the work behind the scenes.

So to do that, we just declare an object like normal, of whatever we need. Their value should be set to null in the class, because it’s better to get the hardware values after initializing so that you can change the configuration file on the fly. It’ll look something like this:

    private DcMotor leftMotor = null;
    private DcMotor rightMotor = null;

    private CRServo spinner = null;
    private Servo hitter = null;

Now we can go into the initialization.

Initialization

Initialization is the first time we’re really going to have to think. Now is the time we’re going to link those objects we just created to actual hardware connected to the robot (based on the configuration file). Remember what we put inside the strings, because that’s the key to linking this to that configuration file, which we will create at the end.

Assigning our objects to real objects

To do that, we’ll set the objects equal to one of these statements: hardwareMap.<object type>.get("<name in config>");, which effectively just sets the object to the actual object in real life that we need it to be. All the motor values and positions and everything matches up with what’s happening in real life, so the object in the code basically becomes the object in real life for all of our purposes. Now we can work with those values.

The whole statements should look something like this:

    leftMotor = hardwareMap.dcMotor.get("left motor");
    spinner = hardwareMap.crservo.get("spinner");

Preparing the motors and servos

Now that they’re set up, it’s a good idea to prepare them to actually be used. Technically this step isn’t required, as there are many other ways to get the motors working properly, but it’s a lot easier to reverse directions and set modes here to make the code easier to read and change.

Setting Directions

The first big thing is to set the motor directions. They can either be forward or reverse, and it’s really difficult to know which one you should use before you test, so I recommend just picking one and seeing if it’s correct when you test for the first time. Anyway, to set, you’re going to use setDirection(<direction>), a method in DcMotor. We don’t really need to understand how this functions directly (although if you’re really interested and have nothing better to do you could probably dig it up somehow), but we do need to understand how to use it.

To use it, you call it on a DcMotor object, with a direction in its parameter. The two directions we have are:

    DcMotor.Direction.REVERSE
    DcMotor.Direction.FORWARD

two constants that correspond to forward and backward. After you’re done, the whole statement should look something like this:

    leftMotor.setDirection(DcMotor.Direction.REVERSE);
    spinner.setDirection(Servo.Direction.FORWARD);

Setting Modes

The other big thing is to set the all the motors’ modes. There are a few big modes for us to keep in mind:

And that’s all of them. So, once you pick the one you think would be most suited for the program, we can actually set it. It’s going to be very similar to setting the direction, just this time we’re using setMode(<mode>). The end result will be something like this:

    leftMotor.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER);
    rightMotor.setMode(DcMotor.RunMode.RUN_TO_POSITION);

You’re all set now! Time to get into the fun stuff!

The Loop

There are many different ways to make the heart of your driver-controlled OpMode, so I’m just going to give you the tools to do so. There are a ton of samples in FtcRobotController or our code from previous years too if you get stuck.

Put all your code inside public void loop(){ ... } for it to run continuously until the driver hits stop on the app.

Gamepad

All of these can be accessed by calling them on gamepad1 or gamepad2, corresponding to whichever controllers did Start + A and Start + B on the app, respectively. Every time the loop runs, it will get the current state of the gamepad, so there’s not need to update it like in RobotC or anything.

There’s also other buttons and values you can get from the gamepad, but they’re rarely used, since we have two gamepads to work with. It’s fun to make a single gamepad version that uses every button possible, but it’s a lot easier to operate with two drivers, so I didn’t include those other buttons. You can still read about them in the documentation provided by FTC at the top.

Using the Motors

There are tons of methods to use, especially with encoders, but the one that is used by far the most is setPower(<power>). It sets a motor’s voltage to nothing (0), full (1), full reverse (-1), and every value in between. Used with the gamepad’s joysticks, it’s easy to translate fully up to full power forwards. Using this with if statements and buttons works particularly well too. It’ll work something like this many times:

    leftMotor.setPower(-gamepad1.left_stick_y);
    rightMotor.setPower(-gamepad1.right_stick_y);

    if(gamepad1.a){
        slider.setPower(1);
    } else if (gamepad1.b) {
        slider.setPower(-1);
    } else {
        slider.setPower(0);
    }

There’s also getPower(), which returns whatever value the power is currently set to. Not super useful when trying to actually move the robot but incredibly nice when debugging and making sure everything works as intended with telemetry…

Telemetry

This is the main way of printing things to the phone. Could be a string, could be a number, almost anything. To do this, we will use telemetry.addData(<title>, <value>).

This takes in two strings as it’s parameters: a title and a value. The title is any string you want, but usually it’s good to just type one in yourself that has some relevance to what it represents. No one’s stopping you from making it a meme though. With a title, it’ll look something like this:

    telemetry.addData("Left Motor", <value>);

The value can be almost anything as well, from Strings to floats. The ones that are used the most are values returned from getPower() and getCurrentPosition() (for encoders) or getPosition() (for servos), which help a lot for making sure that motors and encoders are working as intended. With the value inserted as well, the statement’ll look something like this:

    telemetry.addData("Left Motor", leftMotor.getPower());

All that’s left is to call telemtry.update();. You only do this once inside the loop, otherwise the output will look super janky as it tries to update twice a frame. This just makes sure that all the values get refreshed and prints them to the screen. At the end of a few statements, your code will probably look something like this:

    telemetry.addData("Left Motor", leftMotor.getPower());
    telemetry.addData("Right Motor", rightMotor.getPower());
    telemetry.addData("Spinner", spinner.getPosition());
    telemetry.update();

Recap

Alright, now you know which files you need to import into many TeleOp programs and how to check to make sure you’ve got them all. You also know how to define objects on the robot and link them to their real-world counterparts to allow them to be used in the code. You know how to access values on the gamepad and how to use those to move the motors.

You know how to make a Driver-Controlled OpMode! Nice. :)

If you’re still stuck or want to make sure you’re heading in the right direction, you can check out a driver controlled opmode we’re using in our season’s code.