Simple Examples

Before we get too far, let’s take a look at a few simple examples. The goal of this chapter is to give you a quick peek into what robot code that uses Strongback looks like. Just remember that you’ll probably see some code here that you might not understand, but we’ll definitely cover all of it in later chapters.

Really simple tank drive

Here’s the compmlete code for a really simple tank-drive robot, with one motor on each side driven by a simple arcade-style joystick:

package org.omgrobots.example;

import org.strongback.components.Motor;
import org.strongback.components.ui.ContinuousRange;
import org.strongback.components.ui.FlightStick;
import org.strongback.drive.TankDrive;
import org.strongback.hardware.Hardware;

public class SimpleTankDriveRobot extends edu.wpi.first.wpilibj.IterativeRobot {

    private static final int JOYSTICK_PORT = 1; // in driver station
    private static final int LEFT_MOTOR_PORT = 1;
    private static final int RIGHT_MOTOR_PORT = 2;

    private TankDrive drive;
    private ContinuousRange driveSpeed;
    private ContinuousRange turnSpeed;

    @Override
    public void robotInit() {
        // Set up the robot hardware ...
        Motor left = Hardware.Motors.talon(LEFT_MOTOR_PORT);
        Motor right = Hardware.Motors.talon(RIGHT_MOTOR_PORT).invert();
        drive = new TankDrive(left, right);

        // Set up the human input device ...
        FlightStick joystick = Hardware.HumanInterfaceDevices.logitechAttack3D(JOYSTICK_PORT);
        driveSpeed = joystick.getPitch();
        turnSpeed = joystick.getRoll().invert();
    }

    @Override
    public void teleopInit() {
    }

    @Override
    public void teleopPeriodic() {
        drive.arcade(driveSpeed.read(), turnSpeed.read());
    }

    @Override
    public void disabledInit() {
    }
}

As you can see, there’s not much logic there. The robotInit() method does most of the work, and it really is just setting up some hardware components. The teleopPeriodic() method is called repeatedly by the IterativeRobot base class, and it just reads the drive and turn speeds and sends them to the TankDrive object.

This robot only uses Strongback’s hardare components, and doesn’t use any of the other Strongback features.

Tip
Motors vs motor controllers

Robot code really doesn’t care about what kind of motors are used on the robot. Instead, the code needs to know what kind of motor controllers are used. In our code above, we use the Hardware.Motors.talon(…​) method to create each "motor", although on our robot hardare that really corresponds to a motor and motor controller pair.

Adding more motors

What if our robot had two motors on each side, but was otherwise exactly the same. We’d only have to change our robotInit() method to create a few extra motors:

package org.omgrobots.example;

import org.strongback.components.Motor;
import org.strongback.components.ui.ContinuousRange;
import org.strongback.components.ui.FlightStick;
import org.strongback.drive.TankDrive;
import org.strongback.hardware.Hardware;

public class SimpleTankDriveRobot extends edu.wpi.first.wpilibj.IterativeRobot {

    private static final int JOYSTICK_PORT = 1; // in driver station
    private static final int LF_MOTOR_PORT = 1;
    private static final int LR_MOTOR_PORT = 2;
    private static final int RF_MOTOR_PORT = 3;
    private static final int RR_MOTOR_PORT = 4;

    private TankDrive drive;
    private ContinuousRange driveSpeed;
    private ContinuousRange turnSpeed;

    @Override
    public void robotInit() {
        // Set up the robot hardware ...
        Motor left = Motor.compose(Hardware.Motors.talon(LF_MOTOR_PORT),
                                   Hardware.Motors.talon(LR_MOTOR_PORT));
        Motor right = Motor.compose(Hardware.Motors.talon(RF_MOTOR_PORT),
                                    Hardware.Motors.talon(RR_MOTOR_PORT)).invert();
        drive = new TankDrive(left, right);

        // Set up the human input device ...
        FlightStick joystick = Hardware.HumanInterfaceDevices.logitechAttack3D(JOYSTICK_PORT);
        driveSpeed = joystick.getPitch();
        turnSpeed = joystick.getRoll().invert();
    }

    @Override
    public void teleopInit() {
    }

    @Override
    public void teleopPeriodic() {
        drive.arcade(driveSpeed.read(), turnSpeed.read());
    }

    @Override
    public void disabledInit() {
    }
}

Here, we’re creating two motors on the left and two on the right (with each motor using a Talon motor controller), but we’re using the Motor.compose(Motor,Motor) method to create a single Motor object that wraps two other Motor objects. After all, we’re always going to control the two left motors exactly the same way, so we never need to address them individually. We then pass those two composite Motor objects into the TankDrive constructor.

Everything else is the same!

Tank drive robot with autonomous mode

What if we want to add some autonomous behavior to our robot? To keep things simple, we want our robot to drive forward at 50% speed for 5 seconds. First, we want to configure Strongback in the robotInit() method:

    @Override
    public void robotInit() {
        // Set up Strongback using its configurator. This is entirely optional, but we're not using
        // events or data so it's better if we turn them off. All other defaults are fine.
        Strongback.configure().recordNoEvents().recordNoData().initialize();

        ...
    }

We then want to override the IterativeRobot.autonomousInit() method, so we’ll add this:

    @Override
    public void autonomousInit() {
        // Start Strongback functions ...
        Strongback.start();
        // Submit a command ...
        Strongback.submit(new Command(5.0) {
            @Override
            public boolean execute() {
                drive.tank(0.5, 0.5);
                return false;
            }
            @Override
            public void end() {
                drive.stop();
            }
        });
    }

This Strongback.submit(…​) method takes a Command, which we instantiate as an anonymous class. The resulting command runs for 5 seconds, and each time the command runs the execute() method will drive forward at 50% power. After 5 seconds, the command’s end() method is automatically called and it stops the drive motors.

Tip
Command classes

Although you can create Command subclasses using anonymous classes, it’s usually better to do this only for really simple logic. You will probably make most of your Command classes regular classes, since they’re much easier to test and reuse in both autonomous and teleop modes.

Finally, we have to add some logic to disabledInit() to stop the drive motors (in case the robot is disabled before the 5 second drive time has elapsed) and also shutdown Strongback:

    @Override
    public void disabledInit() {
        drive.stop();
        // Tell Strongback that the robot is disabled so it can flush and kill commands.
        Strongback.disable();
    }

Here’s the complete class:

package org.omgrobots.example;

import org.strongback.Strongback;
import org.strongback.command.Command;
import org.strongback.components.Motor;
import org.strongback.components.ui.ContinuousRange;
import org.strongback.components.ui.FlightStick;
import org.strongback.drive.TankDrive;
import org.strongback.hardware.Hardware;

public class SimpleTankDriveRobot extends edu.wpi.first.wpilibj.IterativeRobot {

    private static final int JOYSTICK_PORT = 1; // in driver station
    private static final int LF_MOTOR_PORT = 1;
    private static final int LR_MOTOR_PORT = 2;
    private static final int RF_MOTOR_PORT = 3;
    private static final int RR_MOTOR_PORT = 4;

    private TankDrive drive;
    private ContinuousRange driveSpeed;
    private ContinuousRange turnSpeed;

    @Override
    public void robotInit() {
        // Set up Strongback using its configurator. This is entirely optional, but we're not using
        // events or data so it's better if we turn them off. All other defaults are fine.
        Strongback.configure().recordNoEvents().recordNoData().initialize();

        // Set up the robot hardware ...
        Motor left = Motor.compose(Hardware.Motors.talon(LF_MOTOR_PORT),
                                   Hardware.Motors.talon(LR_MOTOR_PORT));
        Motor right = Motor.compose(Hardware.Motors.talon(RF_MOTOR_PORT),
                                    Hardware.Motors.talon(RR_MOTOR_PORT)).invert();
        drive = new TankDrive(left, right);

        // Set up the human input device ...
        FlightStick joystick = Hardware.HumanInterfaceDevices.logitechAttack3D(JOYSTICK_PORT);
        driveSpeed = joystick.getPitch();
        turnSpeed = joystick.getRoll().invert();
    }

    @Override
    public void autonomousInit() {
        // Start Strongback functions ...
        Strongback.start();
        Strongback.submit(new Command(5.0) {
            @Override
            public boolean execute() {
                drive.tank(0.5, 0.5);
                return false;
            }
            @Override
            public void end() {
                drive.stop();
            }
        });
    }

    @Override
    public void teleopInit() {
    }

    @Override
    public void teleopPeriodic() {
        drive.arcade(driveSpeed.read(), turnSpeed.read());
    }

    @Override
    public void disabledInit() {
        drive.stop();
        // Tell Strongback that the robot is disabled so it can flush and kill commands.
        Strongback.disable();
    }
}

Add data logging

One other cool feature of Strongback is its data logging system, which records one or more channels of data that you specify. Stronback uses a single thread that runs commands, data and event logging, button monitoring, etc., and that thread does all of this every 20 milliseconds (although you can control the period via Strongback’s configuration).

Let’s add data logging to our robot. First, we want to change Strongback’s configuration to remove the recordNoData() and recordNoEvents() methods, since we now want to record them. The first two lines of robotInit() now become:

    @Override
    public void robotInit() {
        // Set up Strongback using its configurator. All defaults are fine for this example.
        Strongback.configure().initialize();

        ...
    }

And, we want to tell Strongback what to record, and this is done at the end of the robotInit() method:

    @Override
    public void robotInit() {
        ...
        // Set up the data recorder to capture the left & right motor speeds.
        Strongback.dataRecorder()
                  .register("Left motors", left)
                  .register("Right motors", right);
    }

This records one channel called "Left motors" whose values will be the instantaneous speed (in percentage) sent by our code to the left motor controllers, and another channel called "Right motors" whose values will be the instantaneous speed (in percentage) sent by the right motor controllers.

Once again, the data recorder by default will make measurements every 20 milliseconds, so if we run our robot (including autonomous mode) for 2 minutes, we’ll have recorded a time history with 6000 measurements for each motor! We can then disable the robot, copy the data file from the RoboRIO to our computer, and use Strongback’s command line tool to convert the binary data file to a comma-separated (CSV) file:

$ strongback.sh log-decoder -f strongback-data-1.log -o strongback-data-1.csv

Before we go on, though, let’s look at the complete robot code for our hypothetical robot:

package org.omgrobots.example;

import org.strongback.Strongback;
import org.strongback.command.Command;
import org.strongback.components.Motor;
import org.strongback.components.ui.ContinuousRange;
import org.strongback.components.ui.FlightStick;
import org.strongback.drive.TankDrive;
import org.strongback.hardware.Hardware;

public class SimpleTankDriveRobot extends edu.wpi.first.wpilibj.IterativeRobot {

    private static final int JOYSTICK_PORT = 1; // in driver station
    private static final int LF_MOTOR_PORT = 1;
    private static final int LR_MOTOR_PORT = 2;
    private static final int RF_MOTOR_PORT = 3;
    private static final int RR_MOTOR_PORT = 4;

    private TankDrive drive;
    private ContinuousRange driveSpeed;
    private ContinuousRange turnSpeed;

    @Override
    public void robotInit() {
        // Set up Strongback using its configurator. All defaults are fine for this example.
        Strongback.configure().initialize();

        // Set up the robot hardware ...
        Motor left = Motor.compose(Hardware.Motors.talon(LF_MOTOR_PORT),
                                   Hardware.Motors.talon(LR_MOTOR_PORT));
        Motor right = Motor.compose(Hardware.Motors.talon(RF_MOTOR_PORT),
                                    Hardware.Motors.talon(RR_MOTOR_PORT)).invert();
        drive = new TankDrive(left, right);

        // Set up the human input device ...
        FlightStick joystick = Hardware.HumanInterfaceDevices.logitechAttack3D(JOYSTICK_PORT);
        driveSpeed = joystick.getPitch();
        turnSpeed = joystick.getRoll().invert();

        // Set up the data recorder to capture the left & right motor speeds.
        Strongback.dataRecorder()
                  .register("Left motors", left)
                  .register("Right motors", right);
    }

    @Override
    public void autonomousInit() {
        // Start Strongback functions ...
        Strongback.start();
        Strongback.submit(new Command(5.0) {
            @Override
            public boolean execute() {
                drive.tank(0.5, 0.5);
                return false;
            }
            @Override
            public void end() {
                drive.stop();
            }
        });
    }

    @Override
    public void teleopInit() {
    }

    @Override
    public void teleopPeriodic() {
        drive.arcade(driveSpeed.read(), turnSpeed.read());
    }

    @Override
    public void disabledInit() {
        drive.stop();
        // Tell Strongback that the robot is disabled so it can flush and kill commands.
        Strongback.disable();
    }
}

Read to get started?

Hopefully this chapter gave you a little picture of what robot code looks like when it uses Strongback. Of course, we just touched the surface, so continue on with the next chapters to get a much more detailed understanding of all of Strongback’s power.

results matching ""

    No results matching ""