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() {
}
}
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:
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 |
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 |
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.