Mechanisms
A mechanism is one physical part of the robot, written as one Java class. On branch mech-1-Mechanisms you write Arm.java and Flywheel.java: hardware fields, one constructor, two methods.
- A clean build, from Project Setup.
- Fields, constructors and methods, from Java Basics.
- Arm and flywheel working in Tuner X at IDs 31, 32 and 21.
Setup is behind you
The arm and the flywheel work in Tuner X, the project builds, and you know what a mechanism, a command, and the scheduler each do. That is what every page before this one was for. This is the lesson where you start writing the code.
What mechanism are you working on?
The lesson below is written for the one you pick. Switch back any time to read it for the other.
Work in the project you generated in Project Setup. There is nothing to clone, and the file is new.
Both classes live in a mechanisms folder beside Robot.java, at src/main/java/first/robot/mechanisms/. Make the folder, then make the file in it.


The hardware fields
Name the file Arm.javaFlywheel.java and paste this into it. The package line and the imports are the part you cannot work out from a lesson, so they are here in full. The class is empty and it compiles. The three comments mark the three places the rest of this lesson goes.
package first.robot.mechanisms; // The static imports are the ones your editor will not offer to add for you.// Tuner X writes Volts.per(RotationsPerSecond) into the config you paste two// lessons from now, so they are here already and that paste just works.import static org.wpilib.units.Units.RotationsPerSecond;import static org.wpilib.units.Units.RotationsPerSecondPerSecond;import static org.wpilib.units.Units.Volts; import com.ctre.phoenix6.CANBus;import com.ctre.phoenix6.configs.FeedbackConfigs;import com.ctre.phoenix6.configs.MotionMagicConfigs;import com.ctre.phoenix6.configs.MotorOutputConfigs;import com.ctre.phoenix6.configs.Slot0Configs;import com.ctre.phoenix6.configs.TalonFXConfiguration;import com.ctre.phoenix6.controls.MotionMagicVoltage;import com.ctre.phoenix6.controls.VoltageOut;import com.ctre.phoenix6.hardware.CANcoder;import com.ctre.phoenix6.hardware.TalonFX;import com.ctre.phoenix6.signals.FeedbackSensorSourceValue;import com.ctre.phoenix6.signals.GravityTypeValue;import com.ctre.phoenix6.signals.InvertedValue;import com.ctre.phoenix6.signals.NeutralModeValue;import org.wpilib.command3.Mechanism; public class Arm extends Mechanism { // The fields go here. public Arm() { // The motor configuration goes here. } // The two methods go here.}package first.robot.mechanisms; // The static imports are the ones your editor will not offer to add for you.// Tuner X writes Volts.per(RotationsPerSecond) into the config you paste two// lessons from now, so they are here already and that paste just works.import static org.wpilib.units.Units.RotationsPerSecond;import static org.wpilib.units.Units.RotationsPerSecondPerSecond;import static org.wpilib.units.Units.Volts; import com.ctre.phoenix6.CANBus;import com.ctre.phoenix6.configs.MotionMagicConfigs;import com.ctre.phoenix6.configs.MotorOutputConfigs;import com.ctre.phoenix6.configs.Slot0Configs;import com.ctre.phoenix6.configs.TalonFXConfiguration;import com.ctre.phoenix6.controls.MotionMagicVelocityVoltage;import com.ctre.phoenix6.controls.VoltageOut;import com.ctre.phoenix6.hardware.TalonFX;import com.ctre.phoenix6.signals.InvertedValue;import com.ctre.phoenix6.signals.NeutralModeValue;import org.wpilib.command3.Mechanism; public class Flywheel extends Mechanism { // The fields go here. public Flywheel() { // The motor configuration goes here. } // The two methods go here.}Your editor will grey the imports out until you use them. That is expected, and they go quiet one block at a time as you fill the class in. The fields come first.
public class Arm extends Mechanism { private final CANBus canivore = new CANBus("canivore"); private final TalonFX motor = new TalonFX(31, canivore); private final CANcoder encoder = new CANcoder(32, canivore); // Pushes a set voltage at the motor. No sensors involved. private final VoltageOut voltageOut = new VoltageOut(0);public class Flywheel extends Mechanism { private final CANBus canivore = new CANBus("canivore"); private final TalonFX motor = new TalonFX(21, canivore); // Pushes a set voltage at the motor. No sensors involved. private final VoltageOut voltageOut = new VoltageOut(0);extends Mechanismis what makes this a mechanism rather than a plain object. Building one registers it with the scheduler, and it is whererunRepeatedly(...)and the defaultidle()command come from.new CANBus("canivore")names the bus these devices sit on. That string has to match the name you gave the CANivore in Tuner X. Spell it differently and nothing answers.- The CAN IDs come from Motor Setup: 31 and 3221. If your bench came out with different numbers, change the code to match. Do not leave the two disagreeing.
- One wheel, one motor, and no CANcoder. A flywheel is tuned for speed, and the encoder inside the TalonFX already measures speed. The arm needs a second device because an angle has to be right the moment the robot boots.
VoltageOutis a Phoenix 6 control request: an object that says "apply this many volts". Build it once as a field, not fresh every loop.
There is nothing to import. They are all at the top of the file already, and the greyed-out ones stop being grey as you use them. The class compiles at every step from here, because what you pasted was a complete class to begin with.
Configure the motor once
The constructor runs one time, the moment new ArmFlywheel() is evaluated. NeutralMode is the one setting here you choose. Inverted is not a choice: it is the direction you proved on Motor Setup, and the mechanism decided it long before any code ran. Ignore the two Expo values. They are defaults built into every config Tuner X generates, and no workshop uses them.

These are our numbers, not yours
The block below is the shape, not a config to copy. Yours comes off your own bench. Open the config panel in Tuner X, press the three dots, and choose Generate Code. Paste the result over the whole statement. The mechanism is still open loop here, so a fresh config looks much like this one. From Motion Magic on it carries the gains you measured.
public Arm() { final TalonFXConfiguration talonFXCfg = new TalonFXConfiguration() .withMotorOutput( new MotorOutputConfigs() .withNeutralMode(NeutralModeValue.Coast) // easy to move by hand .withInverted(InvertedValue.CounterClockwise_Positive)) .withMotionMagic( new MotionMagicConfigs() .withMotionMagicExpo_kV( Volts.per(RotationsPerSecond).ofNative(0.119999997317791)) .withMotionMagicExpo_kA( Volts.per(RotationsPerSecondPerSecond).ofNative(0.10000000149011612))) .withFeedback( new FeedbackConfigs() .withFeedbackRemoteSensorID(32) .withFeedbackSensorSource(FeedbackSensorSourceValue.RemoteCANcoder)); motor.getConfigurator().apply(talonFXCfg); } public Flywheel() { final TalonFXConfiguration talonFXCfg = new TalonFXConfiguration() .withMotorOutput( new MotorOutputConfigs() .withNeutralMode(NeutralModeValue.Coast) // easy to spin by hand // positive shoots: clockwise from the motor side .withInverted(InvertedValue.Clockwise_Positive)) .withMotionMagic( new MotionMagicConfigs() .withMotionMagicExpo_kV( Volts.per(RotationsPerSecond).ofNative(0.119999997317791)) .withMotionMagicExpo_kA( Volts.per(RotationsPerSecondPerSecond).ofNative(0.10000000149011612))); motor.getConfigurator().apply(talonFXCfg); }Coast, not Brake
Neutral mode is what the motor does when nothing is commanding it. Coast cuts the power and lets the shaft spin freely. Brake makes the motor resist being turned, so the mechanism stays roughly where you left it.
The lesson armflywheel picks Coast, and the branch says why on that line. You will move the arm by handspin the wheel by hand all day. A competition arm carrying weight usually wants Brake, or it drops the instant you disable. A competition flywheel usually keeps Coast. A wheel with that much stored energy braked to a stop punishes the gearbox every cycle.
The CANcoder in the loop
A TalonFX counts its own rotor turns, and that count starts at zero every time the controller powers on. The CANcoder is absolute. It knows the arm's angle the moment it boots.
That one line makes the CANcoder the motor's position source instead of the rotor. Nothing reads a position on this branch. Leave the line out and the motor measures every angle you ask for later from wherever the arm sat at power-on.
Nothing to point the motor at
The arm's config carries a withFeedbackblock, naming the CANcoder as the motor's position source. The flywheel has none, because it has no such device.
A TalonFX counts its own rotor turns, and a rotor count is a fine way to measure speed. It is a poor way to measure an angle, because it starts at zero every power-on. Speed is the only thing this mechanism is ever asked for.
motormotor.getConfigurator().apply(talonFXCfg) sends every setting above to the motor controller in one message. It runs once, in the constructor, because the controller keeps those settings until something changes them.
Two methods
/** * Push the arm with a fixed voltage. Positive voltage moves the arm counter-clockwise. * * @param voltage The voltage to apply. */ private void setVoltage(double voltage) { motor.setControl(voltageOut.withOutput(voltage)); } /** Stop the motor. */ private void stopMotor() { motor.stopMotor(); }} /** * Spin the flywheel with a fixed voltage. * * @param voltage The voltage to apply. */ private void setVoltage(double voltage) { motor.setControl(voltageOut.withOutput(voltage)); } /** Stop the motor. */ private void stopMotor() { motor.stopMotor(); }}voltageOut.withOutput(voltage) sets the number on the request object you built as a field, and motormotor.setControl(...) sends it. The motor holds that request until something replaces it.
Nothing here reads a sensor. Ask for 6 V and you get 6 V, whatever the armflywheel does with it.
Both are private, and nothing calls them yet. The next lesson wraps them in commands, and those are what the rest of the robot gets to use. The stop helper is named stopMotor rather than stop because a command takes that name next lesson.
Hand it to Robot
You have written the class, but nothing has built one yet. Right now Arm.javaFlywheel.java is a file and nothing more. Robot is where it becomes a real object: built once at startup, outliving every mode, and handed to every OpMode that needs a mechanism. Add the two lines.
package first.robot; import first.robot.mechanisms.Arm;import first.robot.mechanisms.Flywheel;import org.wpilib.command3.Scheduler;import org.wpilib.framework.OpModeRobot; public class Robot extends OpModeRobot { // The robot's mechanisms. Public so OpModes can use them. public final Arm arm = new Arm(); public final Flywheel flywheel = new Flywheel(); public Robot() {} @Override public void robotPeriodic() { Scheduler.getDefault().run(); }}public final because every OpMode reaches the mechanisms through the one Robot it is handed, and nothing should ever swap them out.
Building only the armflywheel? Delete the FlywheelArm field and its import. A field that builds a class you never wrote does not compile.
Check your work
Nothing on this branch moves a motor, so the check is a build and three things you can see.
- Run WPILib: Build Robot Code. You should see
BUILD SUCCESSFUL. That is the real check here: every import resolved, and every name you typed exists. - List
src/main/java/first/robot/mechanisms/.Arm.javaFlywheel.javais in it, besideRobot.java. - Search
Arm.javaFlywheel.javaforCommand. Not one line of code on this branch builds one. - In Tuner X, confirm every device answers on the
canivorebus at 31 and 3221. Those have to be the numbers in your constructor.
You should see
- Private final fields on
ArmFlywheel, and a constructor ending ingetConfigurator().apply(talonFXCfg). setVoltageandstopMotor, bothprivate, and no public method but the constructor.
Check yourself
Which statement about the v3 Mechanism base class is correct?
The constructor sets withNeutralMode(NeutralModeValue.Coast). What does that mean, and why this mechanism?
Why does Arm's config name a feedback sensor when nothing on this branch reads a position?
Arm's config ends with a withFeedback block naming CANcoder 32, and Flywheel's has no withFeedback at all. Why not?