Coach & mentor
Alumni & mentor
A new open source Java library
that you can use in your FRC robot code.
You can even become a contributor!
WPILib for Java
RoboRIO
Your robot code
Tests
Unit tests almost always require hardware!
WPILib for Java
RoboRIO
Your robot code
STRONGBACK
Your robot code
Tests
STRONGBACK
STRONGBACK-TESTING
testing without robot hardware!
Unit tests to test individual classes
are easy to write, easy to run.
Run them all the time.
They'll help tell you if you've broken something that used to work.
Drive Subsystem
Ramp Subsystem
Guardrail
Subsystem
Compressor
Subsystem
Vision
Subsystem
Wrist Subsystem
Kicker Subsystem
Arm Subsystem
Drive Subsystem
Create class for subsystem
Add references to hardware objects
Add logic
Drive Subsystem
Tests would instantiate this subsystem
The subsystem would create hardware instances
Fails even before we can test anything
NOT TESTABLE!
DigitalInput
edu.wpi.first.wpilibj
SubsystemX
This is not testable
Switch
isTriggered():boolean
SubsystemX
SubsystemX
HardwareSwitch
DigitalInput
edu.wpi.first.wpilibj
Switch
isTriggered():boolean
SubsystemX
MockSwitch
Switch
isTriggered():boolean
setTriggered():MockSwitch
setNotTriggered():MockSwitch
setTriggered(boolean):MockSwitch
SubsystemXTest
public class SimpleTankDriveRobot extends IterativeRobot {
private TankDrive drive;
private ContinuousRange driveSpeed;
private ContinuousRange turnSpeed;
@Override
public void robotInit() {
...
Motor left = Motor.compose(Hardware.Motors.talon(1), Hardware.Motors.talon(2));
Motor right = Motor.compose(Hardware.Motors.talon(3), Hardware.Motors.talon(4)).invert();
drive = new TankDrive(left, right);
FlightStick joystick = Hardware.HumanInterfaceDevices.logitechAttack3D(1);
driveSpeed = joystick.getPitch();
turnSpeed = joystick.getRoll().invert();
...
}
@Override
public void teleopPeriodic() {
drive.arcade(driveSpeed.read(), turnSpeed.read());
}
}
Robots often do multiple things at once.
Commands provide a very simple, composeable, and testable way to organize the code
to control these different activities.
Commands can be used in both autonomous and teleoperated modes.
// First swivel the arm to the correct angle (within +/- 1 degree) ...
while ( true ) {
double diff = swivelAngle.computeHeadingChangeTo(30.0,1);
if ( diff < 0.0 ) {
swivelMotor.setSpeed(-0.4);
} else if ( diff > 0.0 ) {
swivelMotor.setSpeed(0.4);
} else {
swivelMotor.stop();
break;
}
}
// Raise the shoulder to the correct angle ...
while ( true ) {
double diff = shoulderAngle.getAngleChangeTo(64.0,1);
if ( diff < 0.0 ) {
shoulderMotor.setSpeed(-0.4);
} else if ( shoulderAngle.getAngleChangeTo(64.0,1) > 0.0 ) {
shoulderMotor.setSpeed(0.4);
} else {
shoulderMotor.stop();
break;
}
}
// Move the elbow to the correct angle ...
while ( true ) {
double diff = elbowAngle.getAngleChangeTo(83.0,1);
if ( diff < 0.0 ) {
elbowMotor.setSpeed(-0.4);
} else if ( diff > 0.0 ) {
elbowMotor.setSpeed(0.4);
} else {
elbowMotor.stop();
break;
}
}
// Open the claw ...
claw.retract();
Difficult to make robot do
multiple things at once.
Put each movement into separate command,
and submit the commands to be run
asynchronously
public class SwivelArm extends Command {
private final Motor swivel;
private final Compass angle;
private final double speed;
private final double desiredAngle;
private final double tolerance;
public SwivelArm( Motor swivel, Compass angle, double speed,
double desiredAngleInDegrees, double toleranceInDegrees ) {
super(swivel);
this.swivel = swivel;
this.angle = angle;
this.speed = Math.abs(speed);
this.desiredAngle = desiredAngleInDegrees;
this.tolerance = toleranceInDegrees;
}
@Override
public boolean execute() {
double diff = angle.computeHeadingChangeTo(desiredAngleInDegrees,toleranceInDegrees);
if ( diff == 0.0 ) {
swivel.stop();
return true;
}
if ( diff < 0.0 ) {
swivel.setSpeed(-speed);
} else {
swivel.setSpeed(speed);
}
return false;
}
}
public class OpenClaw extends Command {
private final Solenoid solenoid;
public OpenClaw( Solenoid solenoid ) {
super(solenoid);
this.solenoid = solenoid;
}
@Override
public boolean execute() {
this.solenoid.retract();
return true;
}
}
public class CloseClaw extends SolenoidOperation {
private final Solenoid solenoid;
public CloseClaw( Solenoid solenoid ) {
super(solenoid);
this.solenoid = solenoid;
}
@Override
public boolean execute() {
this.solenoid.extend();
return true;
}
}
Strongback.submit(new SwivelArm(swivelMotor,swivelAngle,0.4,30.0,1.0));
Strongback.submit(new RotateShoulder(shoulderMotor,shoulderAngle,0.4,64.0,1.0));
Strongback.submit(new RotateElbow(elbowMotor,elbowAngle,0.4,83.0,1.0));
Strongback.submit(new OpenClaw(claw));
Submit them when needed
in teleop or autonomous
They'll all run concurrently
and in the background.
They'll even stop if new commands
with same requirements are submitted.
Create a command group that
consists of other commands but
but runs as a single command.
CommandGroup.runSimultaneously(
new SwivelArm(swivel,swivelAngle,speed,swivelTargetAngle,tolerance));
new RotateShoulder(shoulder,shoulderAngle,speed,shoulderTargetAngle,tolerance));
new RotateElbow(elbow,elbowAngle,speed,elbowTargetAngle,tolerance));
openClaw ? new OpenClaw(claw) : new CloseClaw(claw));
);
Register functions that should be called when a switch changes state.
SwitchReactor reactor = Strongback.switchReactor();
Gamepad gamepad = ...
reactor.onTriggered(gamepad.getRightTrigger(),
()->Strongback.submit(new FireRepeatedlyCommand()));
reactor.onUnTriggered(gamepad.getRightTrigger(),
()->Strongback.submit(new StopFireCommand()));
SwitchReactor reactor = Strongback.switchReactor();
Gamepad gamepad = ...
reactor.onTriggeredSubmit(gamepad.getRightTrigger(),
FireRepeatedlyCommand::new);
reactor.onUnTriggeredSubmit(gamepad.getRightTrigger(),
StopFireCommand::new);
or
Multiple channels of discrete numeric values
recorded at specific, regular intervals
spurious and infrequent events.
and
Records automatically on a constant interval
but you have to give it things to measure.
public interface DataRecorder {
DataRecorder register(String name, IntSupplier supplier);
DataRecorder register(String name, double scale,
DoubleSupplier supplier) {...}
DataRecorder register(String name, Switch swtch);
DataRecorder register(String name, SpeedSensor sensor);
}
Strongback.dataRecorder().register(...);
called with
public class SimpleTankDriveRobot extends IterativeRobot {
@Override
public void robotInit() {
Strongback.configure()
.useExecutionPeriod(20, TimeUnit.MILLISECONDS)
.initialize();
...
Strongback.dataRecorder()
.register("Battery Volts",1000, battery)
.register("Current load", 1000, current)
.register("Left Motors", left)
.register("Right Motors", right)
.register("Trigger", trigger)
.register("Throttle", 1000, throttle::read)
.register("Drive Speed", 1000, driveSpeed::read)
.register("Turn Speed", 1000, turnSpeed::read)
.register("X-Accel", 1000, xAccel::getAcceleration)
.register("Y-Accel", 1000, yAccel::getAcceleration)
.register("Z-Accel", ()->zAccel.getAcceleration()*1000));
}
You have to give it every event
you want recorded.
public class SimpleTankDriveRobot extends IterativeRobot {
@Override
public void robotInit() {
...
FlightStick joystick = Hardware.HumanInterfaceDevices.logitechAttack3D(1);
...
// Capture the state change of the trigger ...
Strongback.switchReactor().
.onTriggered(joystick.getTrigger(),
()->Strongback.eventRecorder().record("Trigger",true))
.onUnTriggered(joystick.getTrigger(),
()->Strongback.eventRecorder().record("Trigger",false));
...
}
Strongback.eventRecorder().record("LaunchEvent",true);
or
Strongback records to a memory mapped file (very fast and efficient) on the RoboRIO.
After disabling the robot, you can download
the binary file, convert it to CSV with
$ strongback.sh log-decoder data.log
and then ...
with your tool of choice.
(Few will need this.)
Sometimes you want to run something
in the background
every cycle of the robot
(about every 20ms)
Really, the Executor is what
Strongback uses to run
commands, the switch reactor, and the data & event recorders.
But you can use it if you want.
@FunctionalInterface
public interface Executable {
/**
* Perform an execution at a given moment within the robot match.
*
* @param timeInMillis the time in the match (in milliseconds)
* when this execution is being called
*/
public void execute(long timeInMillis);
}
Executable myComponent = ...
Strongback.executor().register(myComponent);
Executable myComponent = ...
Strongback.executor().unregister(myComponent);
Strongback runs all asynchronous code on a single thread.
If you put too much on that thread,
it won't run fast enough and
you'll see this error message in the log:
ERROR: Unable to execute all activities within 20 milliseconds!
Strongback.logger().debug("Got to here!");
or
try {
...
} catch ( Throwable t ) {
Strongback.logger().error(t,"Whoa, what happened?!");
}
or
Strongback.logger().warn("Something may have gone wrong!");
$ unzip strongback-1.0.0.zip -d ~/
Simply unzip or untar
into your home directory
$ curl -OL https://github.com/strongback/strongback-java/releases/download/v1.0.0/strongback-1.0.0.zip
$ unzip strongback-1.0.0.zip -d ~/
$ export PATH=~/strongback/java/bin:$PATH
$ cd path/to/eclipse/workspace
$ strongback.sh new-project -e -p com.myteam.robot -r MyRobotProject
$ cd MyRobotProject
$ ant clean compile test deploy
or
$ ant clean test deploy
Give it a try
and join the community!