Building a Smart Intake
Use beam break sensors to automatically stop your intake when the robot is full of game pieces.
By the end you can
- Read multiple DigitalChannels in an IntakeSubsystem.
- Write helper methods to check sensor states.
- Create a SmartIntakeCommand that finishes automatically when full.
During a chaotic FTC match, it's very easy for a driver to accidentally intake too many game pieces and incur a penalty. Or, they might leave the intake spinning violently after the piece is already secured, wasting battery and damaging the game pieces.
We can solve this by building a Smart Intake. We will place a beam break sensor at the very top of our intake throat. When the game piece reaches the top, the beam breaks, and the code automatically stops the motors!
1. The Subsystem Logic
Let's look at how the Unearthed-Alberta repository sets up the IntakeSubsystem.java. The team uses three beam breaks (Low, Mid, High) to track multiple pieces, but we will focus on the High sensor for this example.
package org.firstinspires.ftc.teamcode.subsystems;
import com.arcrobotics.ftclib.command.SubsystemBase;
import com.qualcomm.robotcore.hardware.DcMotor;
import com.qualcomm.robotcore.hardware.DigitalChannel;
import com.qualcomm.robotcore.hardware.HardwareMap;
public class IntakeSubsystem extends SubsystemBase {
private final DcMotor intakeMotor;
private final DigitalChannel beamBreakHigh;
public IntakeSubsystem(HardwareMap hardwareMap) {
intakeMotor = hardwareMap.get(DcMotor.class, "intakeMotor");
// 1. Initialize the sensor
beamBreakHigh = hardwareMap.get(DigitalChannel.class, "ballSensorHigh");
// 2. VERY IMPORTANT: Set it as an input!
beamBreakHigh.setMode(DigitalChannel.Mode.INPUT);
}
public void startIntake() {
intakeMotor.setPower(1.0);
}
public void stopIntake() {
intakeMotor.setPower(0.0);
}
/**
* Helper method to check if the high sensor is blocked by a game piece.
* Our beam breaks are wired Normally Closed, meaning they return FALSE when blocked!
*/
public boolean isHighSensorBlocked() {
// We use the ! (NOT) operator to invert the logic so it's easier to read
return !beamBreakHigh.getState();
}
}Notice the helper method isHighSensorBlocked(). Because the sensors are wired Normally Closed, beamBreakHigh.getState() returns false when the beam is broken by a ball.
We use the ! operator to flip it. Now, if the beam is broken, isHighSensorBlocked() returns true, which is much easier for our brains to understand when writing the Command!
2. The Command Logic
Now we need a Command that turns on the intake, waits for the sensor to trigger, and then stops.
package org.firstinspires.ftc.teamcode.commands;
import com.arcrobotics.ftclib.command.CommandBase;
import org.firstinspires.ftc.teamcode.subsystems.IntakeSubsystem;
public class SmartIntakeCommand extends CommandBase {
private final IntakeSubsystem intake;
public SmartIntakeCommand(IntakeSubsystem intake) {
this.intake = intake;
addRequirements(intake);
}
@Override
public void initialize() {
// Start spinning the motors immediately
intake.startIntake();
}
@Override
public void execute() {
// Nothing needs to happen here. The motors are already running!
}
@Override
public boolean isFinished() {
// The command finishes the exact millisecond this returns true!
return intake.isHighSensorBlocked();
}
@Override
public void end(boolean interrupted) {
// When isFinished() returns true, this runs automatically.
// Or, if the driver cancels the command early (interrupted), this runs.
intake.stopIntake();
}
}This is the beauty of FTCLib's Command-Based architecture. The isFinished() method constantly polls the sensor. The very instant isHighSensorBlocked() evaluates to true, the Command Scheduler kills the command, jumping straight to end(), which cuts power to the motors.
Check Your Understanding
Check yourself