diff --git a/docs/code_examples/basics/autonomous/Autonomous.java b/docs/code_examples/basics/autonomous/Autonomous.java new file mode 100644 index 0000000..5c4325e --- /dev/null +++ b/docs/code_examples/basics/autonomous/Autonomous.java @@ -0,0 +1,18 @@ +// --8<-- [start:full-example] +package frc.robot.commands; + +import edu.wpi.first.wpilibj2.command.SequentialCommandGroup; +import edu.wpi.first.wpilibj2.command.WaitCommand; +import frc.robot.subsystems.Drivetrain; +import frc.robot.RobotPreferences; + +public class Autonomous extends SequentialCommandGroup { + public Autonomous(Drivetrain drivetrain) { + addCommands( + new DriveDistance(drivetrain, RobotPreferences.autoDriveDistance()), + new WaitCommand(RobotPreferences.autoDelay()), + new ShooterUp() + ); + } +} +// --8<-- [end:full-example] diff --git a/docs/code_examples/basics/autonomous/DriveDistance.java b/docs/code_examples/basics/autonomous/DriveDistance.java new file mode 100644 index 0000000..a24a10e --- /dev/null +++ b/docs/code_examples/basics/autonomous/DriveDistance.java @@ -0,0 +1,48 @@ +// --8<-- [start:full-example] +package frc.robot.commands; + +import edu.wpi.first.wpilibj2.command.Command; +import frc.robot.subsystems.Drivetrain; +import frc.robot.RobotPreferences; + +public class DriveDistance extends Command { + + private final Drivetrain drivetrain; + // --8<-- [start:distance-field] + private final double distance; + // --8<-- [end:distance-field] + + public DriveDistance(Drivetrain drivetrain, double inches) { + this.drivetrain = drivetrain; + // --8<-- [start:constructor-body] + this.distance = inches; + addRequirements(drivetrain); + // --8<-- [end:constructor-body] + } + + @Override + public void initialize() { + drivetrain.resetDriveEncoder(); + } + + // Called repeatedly when this Command is scheduled to run + @Override + public void execute() { + drivetrain.arcadeDrive(RobotPreferences.driveDistanceSpeed(), 0.0); + } + + // Make this return true when this Command no longer needs to run execute() + @Override + public boolean isFinished() { + // --8<-- [start:is-finished-body] + return drivetrain.getDriveEncoderDistance() >= distance; + // --8<-- [end:is-finished-body] + } + + // Called once after isFinished returns true, or if the command is interrupted + @Override + public void end(boolean interrupted) { + drivetrain.arcadeDrive(0.0, 0.0); + } +} +// --8<-- [end:full-example] diff --git a/docs/code_examples/basics/autonomous/RobotContainer.java b/docs/code_examples/basics/autonomous/RobotContainer.java new file mode 100644 index 0000000..9039fea --- /dev/null +++ b/docs/code_examples/basics/autonomous/RobotContainer.java @@ -0,0 +1,15 @@ +package frc.robot; + +import edu.wpi.first.wpilibj2.command.Command; +import frc.robot.commands.Autonomous; +import frc.robot.subsystems.Drivetrain; + +public class RobotContainer { + private final Drivetrain m_drivetrain = new Drivetrain(); + + // --8<-- [start:get-autonomous-command] + public Command getAutonomousCommand() { + return new Autonomous(m_drivetrain); + } + // --8<-- [end:get-autonomous-command] +} diff --git a/docs/code_examples/basics/autonomous/RobotPreferences.java b/docs/code_examples/basics/autonomous/RobotPreferences.java new file mode 100644 index 0000000..5be532e --- /dev/null +++ b/docs/code_examples/basics/autonomous/RobotPreferences.java @@ -0,0 +1,22 @@ +package frc.robot; + +import edu.wpi.first.wpilibj.Preferences; + +public class RobotPreferences { + + // --8<-- [start:drive-distance-speed] + public static double driveDistanceSpeed() { + return Preferences.getDouble("driveDistanceSpeed", 0.5); + } + // --8<-- [end:drive-distance-speed] + + // --8<-- [start:auto-delay-and-distance] + public static double autoDelay() { + return Preferences.getDouble("autoDelay", 5.0); + } + + public static double autoDriveDistance() { + return Preferences.getDouble("autoDriveDistance", 12.0); + } + // --8<-- [end:auto-delay-and-distance] +} diff --git a/docs/code_examples/ctre_swerve/RobotContainer.java b/docs/code_examples/ctre_swerve/RobotContainer.java new file mode 100644 index 0000000..00a6be9 --- /dev/null +++ b/docs/code_examples/ctre_swerve/RobotContainer.java @@ -0,0 +1,92 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package frc.robot; + +import static edu.wpi.first.units.Units.MetersPerSecond; +import static edu.wpi.first.units.Units.RadiansPerSecond; +import static edu.wpi.first.units.Units.RotationsPerSecond; + +import com.ctre.phoenix6.swerve.SwerveModule.DriveRequestType; +import com.ctre.phoenix6.swerve.SwerveRequest; +import edu.wpi.first.math.geometry.Rotation2d; +import edu.wpi.first.wpilibj2.command.Command; +import edu.wpi.first.wpilibj2.command.Commands; +import edu.wpi.first.wpilibj2.command.button.CommandXboxController; +import edu.wpi.first.wpilibj2.command.button.RobotModeTriggers; +import frc.robot.generated.TunerConstants; +import frc.robot.subsystems.CommandSwerveDrivetrain; + +public class RobotContainer +{ + // --8<-- [start:fields] + // Top speed and angular rate the joysticks will command. kSpeedAt12Volts comes from the + // generated TunerConstants and reflects your robot's actual measured free speed. + private double MaxSpeed = TunerConstants.kSpeedAt12Volts.in(MetersPerSecond); + private double MaxAngularRate = RotationsPerSecond.of(0.75).in(RadiansPerSecond); + + // A SwerveRequest is a reusable, mutable "what should the drivetrain do right now" object. + // FieldCentric drives relative to the field; a 10% deadband ignores joystick noise near zero. + private final SwerveRequest.FieldCentric drive = new SwerveRequest.FieldCentric() + .withDeadband(MaxSpeed * 0.1) + .withRotationalDeadband(MaxAngularRate * 0.1) + .withDriveRequestType(DriveRequestType.OpenLoopVoltage); + + // Locks the wheels into an "X" pattern so the robot resists being pushed. + private final SwerveRequest.SwerveDriveBrake brake = new SwerveRequest.SwerveDriveBrake(); + + // Points every wheel in a given direction without driving — useful for diagnostics. + private final SwerveRequest.PointWheelsAt point = new SwerveRequest.PointWheelsAt(); + + private final CommandXboxController driverXbox = new CommandXboxController(0); + + // Built from the generator's TunerConstants — this one call replaces the entire + // hand-written motor/encoder/kinematics setup a non-generated swerve project needs. + private final CommandSwerveDrivetrain drivetrain = TunerConstants.createDrivetrain(); + // --8<-- [end:fields] + + public RobotContainer() + { + configureBindings(); + } + + // --8<-- [start:configure-bindings] + private void configureBindings() + { + // Default command: continuously drive field-centric off the joystick. + // X is forward, Y is left (WPILib convention) — axes are negated because + // joysticks report negative Y when pushed forward. + drivetrain.setDefaultCommand( + drivetrain.applyRequest(() -> + drive.withVelocityX(-driverXbox.getLeftY() * MaxSpeed) + .withVelocityY(-driverXbox.getLeftX() * MaxSpeed) + .withRotationalRate(-driverXbox.getRightX() * MaxAngularRate) + ) + ); + + // Apply the drivetrain's configured neutral mode while disabled. + final SwerveRequest.Idle idle = new SwerveRequest.Idle(); + RobotModeTriggers.disabled().whileTrue( + drivetrain.applyRequest(() -> idle).ignoringDisable(true) + ); + + // Hold A to brake (lock wheels in an X). + driverXbox.a().whileTrue(drivetrain.applyRequest(() -> brake)); + + // Hold B to point all wheels toward the left stick's direction. + driverXbox.b().whileTrue(drivetrain.applyRequest(() -> + point.withModuleDirection(new Rotation2d(-driverXbox.getLeftY(), -driverXbox.getLeftX())) + )); + + // Re-zero field-centric heading — press this when the robot's "forward" drifts from + // the field's forward, e.g. at the start of teleop. + driverXbox.leftBumper().onTrue(drivetrain.runOnce(drivetrain::seedFieldCentric)); + } + // --8<-- [end:configure-bindings] + + public Command getAutonomousCommand() + { + return Commands.none(); + } +} diff --git a/docs/code_examples/ctre_swerve/TunerConstants.java b/docs/code_examples/ctre_swerve/TunerConstants.java new file mode 100644 index 0000000..fbf9b58 --- /dev/null +++ b/docs/code_examples/ctre_swerve/TunerConstants.java @@ -0,0 +1,92 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +// This file is a TRIMMED, illustrative excerpt of what the CTRE Tuner X Swerve Project +// Generator actually produces in src/main/java/frc/robot/generated/TunerConstants.java. +// Do not hand-type this file — run the generator (see the tutorial) and let it fill in +// the real CAN IDs, gear ratios, and CANcoder offsets measured from your own robot. +// Full reference: https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-swerve/index.html +package frc.robot.generated; + +import static edu.wpi.first.units.Units.Inches; +import static edu.wpi.first.units.Units.Rotations; + +import com.ctre.phoenix6.CANBus; +import com.ctre.phoenix6.configs.CANcoderConfiguration; +import com.ctre.phoenix6.configs.TalonFXConfiguration; +import com.ctre.phoenix6.swerve.SwerveDrivetrainConstants; +import com.ctre.phoenix6.swerve.SwerveModuleConstants; +import com.ctre.phoenix6.swerve.SwerveModuleConstantsFactory; +import edu.wpi.first.units.measure.Angle; +import edu.wpi.first.units.measure.Distance; +import frc.robot.subsystems.CommandSwerveDrivetrain; + +public class TunerConstants +{ + // --8<-- [start:canbus] + // The CAN bus every drivetrain device lives on: "rio" for the roboRIO's native bus, + // or the name assigned to a CANivore. + public static final CANBus kCANBus = new CANBus("canivore", "./logs/example.hoot"); + // --8<-- [end:canbus] + + private static final int kPigeonId = 1; + + public static final SwerveDrivetrainConstants DrivetrainConstants = new SwerveDrivetrainConstants() + .withCANBusName(kCANBus.getName()) + .withPigeon2Id(kPigeonId); + + // Gear ratios and wheel radius, generated from your swerve module type selection. + private static final double kDriveGearRatio = 6.746_031_75; + private static final double kSteerGearRatio = 21.428_571_43; + private static final Distance kWheelRadius = Inches.of(2); + + private static final SwerveModuleConstantsFactory ConstantCreator = + new SwerveModuleConstantsFactory() + .withDriveMotorGearRatio(kDriveGearRatio) + .withSteerMotorGearRatio(kSteerGearRatio) + .withWheelRadius(kWheelRadius); + + // --8<-- [start:module-constants] + // Front-Left module: drive/steer motor CAN IDs, CANcoder ID, mounting location, and the + // CANcoder offset measured by the generator's self-test — note this is in ROTATIONS, not + // the degrees YAGSL's absoluteEncoderOffset uses. + private static final int kFrontLeftDriveMotorId = 3; + private static final int kFrontLeftSteerMotorId = 2; + private static final int kFrontLeftEncoderId = 1; + private static final Angle kFrontLeftEncoderOffset = Rotations.of(0.152_343_75); + private static final boolean kFrontLeftSteerMotorInverted = true; + private static final Distance kFrontLeftXPos = Inches.of(10); + private static final Distance kFrontLeftYPos = Inches.of(10); + + public static final SwerveModuleConstants FrontLeft = + ConstantCreator.createModuleConstants( + kFrontLeftSteerMotorId, kFrontLeftDriveMotorId, kFrontLeftEncoderId, kFrontLeftEncoderOffset, + kFrontLeftXPos, kFrontLeftYPos, /* driveInverted */ false, kFrontLeftSteerMotorInverted, /* encoderInverted */ false); + // --8<-- [end:module-constants] + + // FrontRight, BackLeft, and BackRight are generated the exact same way, each with their + // own CAN IDs, mounting location, and measured offset. + public static final SwerveModuleConstants FrontRight = + ConstantCreator.createModuleConstants(0, 1, 0, Rotations.of(-0.487_304_69), + Inches.of(10), Inches.of(-10), true, true, false); + + public static final SwerveModuleConstants BackLeft = + ConstantCreator.createModuleConstants(6, 7, 3, Rotations.of(-0.219_482_42), + Inches.of(-10), Inches.of(10), false, true, false); + + public static final SwerveModuleConstants BackRight = + ConstantCreator.createModuleConstants(4, 5, 2, Rotations.of(0.172_363_28), + Inches.of(-10), Inches.of(-10), true, true, false); + + // --8<-- [start:create-drivetrain] + /** + * Creates the drivetrain with the generated constants for all four modules. + * This should only be called once, from RobotContainer. + */ + public static CommandSwerveDrivetrain createDrivetrain() + { + return new CommandSwerveDrivetrain(DrivetrainConstants, FrontLeft, FrontRight, BackLeft, BackRight); + } + // --8<-- [end:create-drivetrain] +} diff --git a/docs/code_examples/swerve/talonfx/controllerproperties.json b/docs/code_examples/swerve/talonfx/controllerproperties.json new file mode 100644 index 0000000..c5ab644 --- /dev/null +++ b/docs/code_examples/swerve/talonfx/controllerproperties.json @@ -0,0 +1,8 @@ +{ + "angleJoystickRadiusDeadband": 0.5, + "heading": { + "p": 0.4, + "i": 0, + "d": 0.01 + } +} diff --git a/docs/code_examples/swerve/talonfx/modules/backleft.json b/docs/code_examples/swerve/talonfx/modules/backleft.json new file mode 100644 index 0000000..6395d23 --- /dev/null +++ b/docs/code_examples/swerve/talonfx/modules/backleft.json @@ -0,0 +1,26 @@ +{ + "drive": { + "type": "talonfx", + "id": 7, + "canbus": "canivore" + }, + "angle": { + "type": "talonfx", + "id": 8, + "canbus": "canivore" + }, + "encoder": { + "type": "cancoder", + "id": 12, + "canbus": "canivore" + }, + "inverted": { + "drive": false, + "angle": false + }, + "absoluteEncoderOffset": 6.504, + "location": { + "front": -12, + "left": 12 + } +} diff --git a/docs/code_examples/swerve/talonfx/modules/backright.json b/docs/code_examples/swerve/talonfx/modules/backright.json new file mode 100644 index 0000000..73c3229 --- /dev/null +++ b/docs/code_examples/swerve/talonfx/modules/backright.json @@ -0,0 +1,26 @@ +{ + "drive": { + "type": "talonfx", + "id": 5, + "canbus": "canivore" + }, + "angle": { + "type": "talonfx", + "id": 6, + "canbus": "canivore" + }, + "encoder": { + "type": "cancoder", + "id": 11, + "canbus": "canivore" + }, + "inverted": { + "drive": false, + "angle": false + }, + "absoluteEncoderOffset": -18.281, + "location": { + "front": -12, + "left": -12 + } +} diff --git a/docs/code_examples/swerve/talonfx/modules/frontleft.json b/docs/code_examples/swerve/talonfx/modules/frontleft.json new file mode 100644 index 0000000..3d4316e --- /dev/null +++ b/docs/code_examples/swerve/talonfx/modules/frontleft.json @@ -0,0 +1,26 @@ +{ + "drive": { + "type": "talonfx", + "id": 4, + "canbus": "canivore" + }, + "angle": { + "type": "talonfx", + "id": 3, + "canbus": "canivore" + }, + "encoder": { + "type": "cancoder", + "id": 9, + "canbus": "canivore" + }, + "inverted": { + "drive": false, + "angle": false + }, + "absoluteEncoderOffset": -114.609, + "location": { + "front": 12, + "left": 12 + } +} diff --git a/docs/code_examples/swerve/talonfx/modules/frontright.json b/docs/code_examples/swerve/talonfx/modules/frontright.json new file mode 100644 index 0000000..890968a --- /dev/null +++ b/docs/code_examples/swerve/talonfx/modules/frontright.json @@ -0,0 +1,26 @@ +{ + "drive": { + "type": "talonfx", + "id": 2, + "canbus": "canivore" + }, + "angle": { + "type": "talonfx", + "id": 1, + "canbus": "canivore" + }, + "encoder": { + "type": "cancoder", + "id": 10, + "canbus": "canivore" + }, + "inverted": { + "drive": false, + "angle": false + }, + "absoluteEncoderOffset": -50.977, + "location": { + "front": 12, + "left": -12 + } +} diff --git a/docs/code_examples/swerve/talonfx/modules/physicalproperties.json b/docs/code_examples/swerve/talonfx/modules/physicalproperties.json new file mode 100644 index 0000000..bc9b46f --- /dev/null +++ b/docs/code_examples/swerve/talonfx/modules/physicalproperties.json @@ -0,0 +1,23 @@ +{ + "conversionFactors": { + "angle": { + "gearRatio": 12.8, + "factor": 0 + }, + "drive": { + "gearRatio": 6.75, + "diameter": 4, + "factor": 0 + } + }, + "currentLimit": { + "drive": 60, + "angle": 20 + }, + "rampRate": { + "drive": 0.25, + "angle": 0.25 + }, + "wheelGripCoefficientOfFriction": 1.19, + "optimalVoltage": 12 +} diff --git a/docs/code_examples/swerve/talonfx/modules/pidfproperties.json b/docs/code_examples/swerve/talonfx/modules/pidfproperties.json new file mode 100644 index 0000000..75d5528 --- /dev/null +++ b/docs/code_examples/swerve/talonfx/modules/pidfproperties.json @@ -0,0 +1,16 @@ +{ + "drive": { + "p": 1, + "i": 0, + "d": 0, + "f": 0, + "iz": 0 + }, + "angle": { + "p": 50, + "i": 0, + "d": 0.32, + "f": 0, + "iz": 0 + } +} diff --git a/docs/code_examples/swerve/talonfx/swervedrive.json b/docs/code_examples/swerve/talonfx/swervedrive.json new file mode 100644 index 0000000..24e584d --- /dev/null +++ b/docs/code_examples/swerve/talonfx/swervedrive.json @@ -0,0 +1,14 @@ +{ + "imu": { + "type": "pigeon2", + "id": 13, + "canbus": "canivore" + }, + "invertedIMU": true, + "modules": [ + "frontleft.json", + "frontright.json", + "backleft.json", + "backright.json" + ] +} diff --git a/docs/programming/SwerveDriveIntro.md b/docs/programming/SwerveDriveIntro.md index 299b9e4..b69c0f9 100644 --- a/docs/programming/SwerveDriveIntro.md +++ b/docs/programming/SwerveDriveIntro.md @@ -176,7 +176,7 @@ The `absoluteEncoderOffset` tells YAGSL how to interpret the encoder reading. It This file defines your robot's physical parameters used in kinematics calculations: ```json title="physicalproperties.json Example" ---8<-- "docs/code_examples/swerve/neo/physicalproperties.json" +--8<-- "docs/code_examples/swerve/neo/modules/physicalproperties.json" ``` !!! info "Physical Parameters" @@ -190,7 +190,7 @@ This file defines your robot's physical parameters used in kinematics calculatio The `pidfproperties.json` file contains PIDF (Proportional, Integral, Derivative, Feedforward) tuning values for closed-loop motor control: ```json title="pidfproperties.json Example" ---8<-- "docs/code_examples/swerve/neo/pidfproperties.json" +--8<-- "docs/code_examples/swerve/neo/modules/pidfproperties.json" ``` !!! warning "PIDF Tuning is Important" diff --git a/docs/programming/autonomous.md b/docs/programming/autonomous.md index a32d4d5..8ea1ce5 100644 --- a/docs/programming/autonomous.md +++ b/docs/programming/autonomous.md @@ -1,306 +1,161 @@ -# Creating an Autonomous Command - - - -## Overview - -In this section we will be going over: - -1. Creating an autonomous command group -2. Using RobotPreferences to quickly change our autonomous values -3. Using an encoder to autonomously drive -4. Creating a delay timer to pace our commands in autonomous - - - - -*** - -## What Is an Autonomous Command - -- An autonomous command is a command that is ran during "autonomous mode" under the **autonomousInit** method in **Robot.java** -- It could be a single **command** or a **command group** -- It's especially helpful to have if you don't have any cameras to drive the robot during autonomous (rare, but does happen) -- For this tutorial we will create a simple autonomous **command ** that makes the robot drive forward slightly. - -## Creating Commands For Autonomous - -- Since we can't control our robot during an autonomous command we will want to create commands that allow the robot to move independently of a driver - -## Creating a basic Autonomous Command - -!!! abstract "" - **1)** Create a new command called **AutoCommand** using the `create new class/command` feature in Vscode. - - -!!! abstract "" - **2)** Before the constructor create a **double** called **distance** - ```java title="distance field" - private Double distance; - ``` - - - We will use this to tell the command to finish when the robot drives the inputted distance - - **3)** Also create a **Timer** called `runtime`. - - ```java title="runTime field" - private Time runTime; - ``` - - - This will be used to control how long the robot will move for. - -!!! abstract "" - **3)** In the **AutoCommand** constructor add a **DriveSubsystem** parameter called **driveSubsystem** - -!!! abstract "" - **4)** Inside type: - - ```java title="Constructor body" - distance = inches; - ``` - -!!! abstract "" - **5)** In **initialize** add our **resetDriveEncoder** method - - - We want to reset the encoder before we drive so that it counts the distance from zero - -!!! abstract "" - **6)** In **execute** add our **arcadeDrive** method and change the **moveSpeed** parameter to a **RobotPreference** named **driveDistanceSpeed** and **rotateSpeed** to 0.0 - - - We only want to drive the robot forward; a **RobotPreference** will help us tune the drive speed - -!!! abstract "" - **7)** In **isFinished** type: - - ```java title="isFinished() body" - return Robot.m_drivetrain.getDriveEncoderDistance() == distance; - ``` -!!! abstract "" - **8)** In **end** stop the **Drivetrain** and call **end** in **interrupted** - -??? Example - - Your full **DriveDistance.java** should look like this - - ```java title="DriveDistance.java" - package frc.robot.commands; - - import edu.wpi.first.wpilibj2.command.Command; - import frc.robot.subsystems.Drivetrain; - import frc.robot.RobotPreferences; - - public class DriveDistance extends Command { - - private final Drivetrain drivetrain; - private final double distance; - - public DriveDistance(Drivetrain drivetrain, double inches) { - this.drivetrain = drivetrain; - this.distance = inches; - addRequirements(drivetrain); - } - - @Override - public void initialize() { - drivetrain.resetDriveEncoder(); - } - - // Called repeatedly when this Command is scheduled to run - @Override - public void execute() { - drivetrain.arcadeDrive(RobotPreferences.driveDistanceSpeed(), 0.0); - } - - // Make this return true when this Command no longer needs to run execute() - @Override - public boolean isFinished() { - return drivetrain.getDriveEncoderDistance() >= distance; - } - - // Called once after isFinished returns true - @Override - public void end(boolean interrupted) { - drivetrain.arcadeDrive(0.0, 0.0); - } - } - ``` - - The code you typed in **RobotPreferences.java** should be this - - ```java title="RobotPreferences.java" - public static final double driveDistanceSpeed() { - return Preferences.getInstance().getDouble("driveDistanceSpeed", 0.5); - } - ``` - -## Creating The Autonomous Command - -- We will create an **Autonomous command group** with the **DriveDistance** command and the **ShooterPitchUp** command - -!!! abstract "" - **1)** Create a new **Command Group** named **Autonomous** - -!!! abstract "" - **2)** In the constructor type - - ```java title="Autonomous constructor" - addSequential(new DriveDistance(RobotPreferences.autoDriveDistance())); - addSequential(new ShooterUp()); - ``` - - - To add a **command** to run in a **command group** use **addSequential** to execute commands in order - -## Creating the DoDelay Command - -- In order to add timing in between our **commands** in our **command groups** we will need to create a **DoDelay** command -- Unlike regular **delays** the **DoDelay** command will not stall our robot, but wait a certain amount of time before running a command - -!!! abstract "" - **1)** Create a new command called **DoDelay** - -!!! abstract "" - **2)** Before the constructor add two private **doubles** called **expireTime** and **timeout** - -!!! abstract "" - **3)** In the constructor add a **double** called **seconds** in the parameter - -!!! abstract "" - **4)** Inside the constructor set **timeout** equal to **seconds** - -!!! abstract "" - **5)** Create a protected **void** method called **startTimer** - -!!! abstract "" - **6)** Inside set **expireTime** equal to **timeSinceInitialized** + **timeout** - - - This will let the robot know how much time will have passed since the command was initialized when it finishes - -!!! abstract "" - **7)** In **initialized** add our **startTimer** method - -!!! abstract "" - **8)** In **isFinished** return **timeSinceInitialized** is greater or equal to **expireTime** - -??? Example - - Your full **DoDelay.java** should look like this - - ```java title="DoDelay.java" - package frc.robot.commands; - - import edu.wpi.first.wpilibj.command.Command; - - public class DoDelay extends Command { - - private double expireTime; - private double timeout; - - public DoDelay(double seconds) { - // Use requires() here to declare subsystem dependencies - // eg. requires(chassis); - timeout = seconds; - } - - protected void startTimer() { - expireTime = timeSinceInitialized() + timeout; - } - - // Called just before this Command runs the first time - @Override - protected void initialize() { - startTimer(); - } - - // Called repeatedly when this Command is scheduled to run - @Override - protected void execute() { - } - - // Make this return true when this Command no longer needs to run execute() - @Override - protected boolean isFinished() { - return (timeSinceInitialized() >= expireTime); - } - - // Called once after isFinished returns true - @Override - protected void end() { - } - - // Called when another command which requires one or more of the same - // subsystems is scheduled to run - @Override - protected void interrupted() { - } - } - ``` - -## Adding the DoDelay Command to Autonomous.java - -!!! abstract "" - - Add our **DoDelay** command in between **DriveDistance** and **ShooterPitchUp** with a **RobotPreference** called **autoDelay** - -??? Example - - Your full **Autonomous.java** should look like this - - ```java title="Autonomous.java" - package frc.robot.commands; - - import edu.wpi.first.wpilibj.command.CommandGroup; - import frc.robot.RobotPreferences; - - public class Autonomous extends CommandGroup { - /** - * Add your docs here. - */ - public Autonomous() { - addSequential(new DriveDistance(RobotPreferences.autoDriveDistance())); - addSequential(new DoDelay(RobotPreferences.autoDelay())); - addSequential(new ShooterUp()); - } - } - ``` - - The code you typed in **RobotPreferences.java** should look like this - - ```java title="RobotPreferences.java (delay)" - public static double autoDelay() { - return Preferences.getInstance().getDouble("autoDelay", 5.0); - } - - public static double autoDriveDistance() { - return Preferences.getInstance().getDouble("autoDriveDistance", 12.0); - } - ``` - -## Adding Our Autonomous Command to Robot.java - -- In order to run our **Autonomous** command in autonomous we will have to put it in **Robot.java** so that it will run as soon as the robot enters the autonomous mode - -- In **Robot.java** under **autonomousInit** find **m_autonomousCommand = m_chooser.getSelected();** and change it to - - !!! note "Why not use the chooser?" - `SendableChooser` allows selecting between multiple autonomous routines from the dashboard at match start, which is useful when you have several autonomous options. For this tutorial we only have one autonomous routine, so using the chooser would add boilerplate (creating options, registering them, fetching the selection) without any benefit. Once you have multiple routines worth choosing from, replacing this line with a `SendableChooser` is a natural next step. - - ```java title="Robot.java" - public void autonomousInit() { - m_autonomousCommand = new Autonomous(); - ... - ``` - -## Testing Our Autonomous Command - -- Now that we have finished coding our **Autonomous** command deploy code and add our new **RobotPreferences** to the widget in **Elastic** -- We have three preferences that change our autonomous behavior **driveDistanceSpeed**, **autoDriveDistance** and **autoDelay** -- **driveDistanceSpeed** will determine the **direction** and how **fast** the robot drives -- **autoDriveDistance** will determine how many **inches** the robot drives **forward** or **backward** -- **autoDelay** will determine how long the robot **waits** before executing **ShooterPitchUp** -- Change these values before enabling your robot in autonomous to make you get the desired results - -## Tips For Debugging Our Autonomous Command - -- If the robot doesn't seem to stop moving or drive in the right direction check for inversions in your **Drive** commands or in the **Drivetrain** subsystem - - You may also need to check if your **encoder** is working, if there are inversions, or if you are using the **getEncoderCount** method instead of the **getEncoderDistanceMethod** -- If your robot doesn't move make sure you typed in the **RobotPreference** names exactly or check your talon IDs/Connection -- If nothing happens after your robot is finished driving check your **autoDelay** preference and whether your **Shooter piston** is already actuated or if your solenoids are working +# Creating an Autonomous Command + + + +## Overview + +In this section we will be going over: + +1. Creating an autonomous command group +2. Using RobotPreferences to quickly change our autonomous values +3. Using an encoder to autonomously drive +4. Using WaitCommand to pace our commands in autonomous + + + + +*** + +## What Is an Autonomous Command + +- An autonomous command is a command that is ran during "autonomous mode" under the **autonomousInit** method in **Robot.java** +- It could be a single **command** or a **command group** +- It's especially helpful to have if you don't have any cameras to drive the robot during autonomous (rare, but does happen) +- For this tutorial we will create a simple autonomous **command ** that makes the robot drive forward slightly. + +## Creating Commands For Autonomous + +- Since we can't control our robot during an autonomous command we will want to create commands that allow the robot to move independently of a driver + +## Creating a basic Autonomous Command + +!!! abstract "" + **1)** Create a new command called **DriveDistance** using the `create new class/command` feature in Vscode. + +!!! abstract "" + **2)** Before the constructor create a **final double** called **distance** + ```java title="distance field" + --8<-- "docs/code_examples/basics/autonomous/DriveDistance.java:distance-field" + ``` + + - We will use this to tell the command to finish when the robot drives the inputted distance + +!!! abstract "" + **3)** In the **DriveDistance** constructor add a **Drivetrain** parameter called **drivetrain** + +!!! abstract "" + **4)** Inside the constructor type: + + ```java title="Constructor body" + --8<-- "docs/code_examples/basics/autonomous/DriveDistance.java:constructor-body" + ``` + + - **addRequirements** tells the scheduler this command needs the **Drivetrain**, so no other command using the drivetrain can run at the same time + +!!! abstract "" + **5)** In **initialize** add our **resetDriveEncoder** method + + - We want to reset the encoder before we drive so that it counts the distance from zero + +!!! abstract "" + **6)** In **execute** add our **arcadeDrive** method and change the **moveSpeed** parameter to a **RobotPreference** named **driveDistanceSpeed** and **rotateSpeed** to 0.0 + + - We only want to drive the robot forward; a **RobotPreference** will help us tune the drive speed + +!!! abstract "" + **7)** In **isFinished** type: + + ```java title="isFinished() body" + --8<-- "docs/code_examples/basics/autonomous/DriveDistance.java:is-finished-body" + ``` +!!! abstract "" + **8)** In **end(boolean interrupted)** stop the **Drivetrain** + + - Command v2 merges the old **end** and **interrupted** methods into a single `end(boolean interrupted)` method that always runs when the command finishes, whether it finished normally or was interrupted by another command + +??? Example + + Your full **DriveDistance.java** should look like this + + ```java title="DriveDistance.java" + --8<-- "docs/code_examples/basics/autonomous/DriveDistance.java:full-example" + ``` + + The code you typed in **RobotPreferences.java** should be this + + ```java title="RobotPreferences.java" + --8<-- "docs/code_examples/basics/autonomous/RobotPreferences.java:drive-distance-speed" + ``` + +## Creating The Autonomous Command + +- We will create an **Autonomous command group** with the **DriveDistance** command and the **ShooterUp** command + +!!! abstract "" + **1)** Create a new class named **Autonomous** that extends `SequentialCommandGroup` + +!!! abstract "" + **2)** Give the constructor a **Drivetrain** parameter called **drivetrain** + +!!! abstract "" + **3)** In the constructor body call **addCommands**, passing in `new DriveDistance(drivetrain, RobotPreferences.autoDriveDistance())` followed by `new ShooterUp()` + + - **addCommands** runs the commands passed to it one at a time, in order, waiting for each one to finish before starting the next + +## Adding a Delay Between Commands + +- In order to pace the commands in our **command group** we need something that runs in between **DriveDistance** and **ShooterUp** and simply waits +- Command v2 already includes a command for this — `WaitCommand` — so there's no need to hand-write our own delay command like older WPILib tutorials did + +!!! abstract "" + **1)** In **Autonomous.java** import `edu.wpi.first.wpilibj2.command.WaitCommand` + +!!! abstract "" + **2)** In **addCommands**, add a **WaitCommand** between **DriveDistance** and **ShooterUp**, giving it a **RobotPreference** called **autoDelay** + + - `WaitCommand(seconds)` finishes on its own after the given number of seconds without blocking the rest of the robot code, unlike a regular `Thread.sleep` + +??? Example + + Your full **Autonomous.java** should look like this + + ```java title="Autonomous.java" + --8<-- "docs/code_examples/basics/autonomous/Autonomous.java:full-example" + ``` + + The code you typed in **RobotPreferences.java** should look like this + + ```java title="RobotPreferences.java (delay)" + --8<-- "docs/code_examples/basics/autonomous/RobotPreferences.java:auto-delay-and-distance" + ``` + +## Adding Our Autonomous Command to RobotContainer.java + +- In order to run our **Autonomous** command group during autonomous, `RobotContainer` needs to hand it to `Robot.java` through `getAutonomousCommand()` + +- In **RobotContainer.java** find `getAutonomousCommand()` and change it to + + !!! note "Why not use the chooser?" + `SendableChooser` allows selecting between multiple autonomous routines from the dashboard at match start, which is useful when you have several autonomous options. For this tutorial we only have one autonomous routine, so using the chooser would add boilerplate (creating options, registering them, fetching the selection) without any benefit. Once you have multiple routines worth choosing from, replacing this line with a `SendableChooser` is a natural next step. + + ```java title="RobotContainer.java" + --8<-- "docs/code_examples/basics/autonomous/RobotContainer.java:get-autonomous-command" + ``` + + - `m_drivetrain` here is the `Drivetrain` subsystem instance already declared in `RobotContainer` + +## Testing Our Autonomous Command + +- Now that we have finished coding our **Autonomous** command deploy code and add our new **RobotPreferences** to the widget in **Elastic** +- We have three preferences that change our autonomous behavior **driveDistanceSpeed**, **autoDriveDistance** and **autoDelay** +- **driveDistanceSpeed** will determine the **direction** and how **fast** the robot drives +- **autoDriveDistance** will determine how many **inches** the robot drives **forward** or **backward** +- **autoDelay** will determine how long the robot **waits** before executing **ShooterUp** +- Change these values before enabling your robot in autonomous to make you get the desired results + +## Tips For Debugging Our Autonomous Command + +- If the robot doesn't seem to stop moving or drive in the right direction check for inversions in your **Drive** commands or in the **Drivetrain** subsystem + - You may also need to check if your **encoder** is working, if there are inversions, or if you are using the **getEncoderCount** method instead of the **getEncoderDistanceMethod** +- If your robot doesn't move make sure you typed in the **RobotPreference** names exactly or check your talon IDs/Connection +- If nothing happens after your robot is finished driving check your **autoDelay** preference and whether your **Shooter piston** is already actuated or if your solenoids are working diff --git a/docs/programming/ctre_swerve_generator_tutorial.md b/docs/programming/ctre_swerve_generator_tutorial.md new file mode 100644 index 0000000..76b85f5 --- /dev/null +++ b/docs/programming/ctre_swerve_generator_tutorial.md @@ -0,0 +1,184 @@ +# Building a Swerve Drivetrain with the CTRE Swerve Project Generator + + + +A guide to CTRE's own code-generation workflow for swerve drivetrains, and how to wire the generated code into a command-based FRC robot project. + +*** + +## Overview + +If your entire swerve drivetrain is CTRE hardware — TalonFX (or TalonFXS) drive/steer motors, CANcoders, and a Pigeon 2 — Tuner X can generate a complete, tuned drivetrain subsystem for you directly from Phoenix 6, without any third-party library. This is CTRE's **Swerve Project Generator**, and it's a different approach from the [YAGSL tutorial](yagsl_swerve_tutorial.md) already on this site. + +!!! abstract "YAGSL vs. the CTRE Swerve Generator" + - **YAGSL** is a community library that reads JSON files at runtime and works with mixed hardware (REV, CTRE, Redux, etc.). It's a good default if your team's hardware varies year to year, or you mix vendors. + - **CTRE's generator** writes real, compiled Java (`TunerConstants.java` + `CommandSwerveDrivetrain.java`) directly against the Phoenix 6 API. It only supports CTRE devices, but in exchange you get first-party support and direct access to Phoenix 6/Pro features (advanced closed-loop control, CAN FD odometry rates, etc.) that YAGSL's abstraction layer doesn't expose. + + Neither is "better" — Make your choice based on your robot hardware and how much you want direct control over the motors and the Phoenix 6 API versus a simpler, more abstracted JSON config with YAGSL. + +This page assumes you're comfortable with the [swerve drive concepts](SwerveDriveIntro.md) (holonomic motion, field- vs. robot-oriented driving, kinematics) already covered on this site — it focuses on what's specific to CTRE's generator. + +*** + +## Prerequisites + +- **[CTRE Tuner X](https://v6.docs.ctr-electronics.com/en/stable/docs/installation/installation-frc.html){target=_blank}** installed and up to date for the current season. +- **Phoenix 6 vendordep** added to your robot project (see [3rd Party Libraries](../setup/3rd_party_libs.md)): `https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-frc2025-latest.json` +- All swerve hardware must be CTRE: **TalonFX or TalonFXS** for both drive and steer motors, **CANcoder** for absolute position, and a **Pigeon 2** for heading. +- Firmware on every device, and your Tuner X version, must match the current season's Phoenix 6 release — mismatched versions are a common source of generator failures. + +!!! note "Exact version requirements" + CTRE's requirements change with each season's Phoenix 6 release. Check [Swerve System Requirements](https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-swerve/swerve-system-requirements.html){target=_blank} for the current version matrix rather than relying on this page — it will go stale faster than CTRE's own docs. + +*** + +## Running the Generator + +The Swerve Project Generator lives inside PhoenixTuner X, under the **Mechanisms** tab. At a high level, it walks you through: + +!!! abstract "What the generator asks for" + - Your **swerve module type** (e.g. a supported WCP/SDS pre-made module, or a fully custom configuration) — gear ratios are filled in for you if you pick a a WCP or SDS module. + - **Per-module CAN IDs** for each drive motor, steer/turning motor, and CANcoder. CTRE's convention is Front-Left (1, 2, 3), Front-Right (4, 5, 6), Back-Left (7, 8, 9), Back-Right (10, 11, 12) for (drive, steer/turn, encoder) — This numbering pattern is not required, but IDs must be unique across the whole CAN bus. + - **CANcoder offsets**, which the generator measures for you via a self-test rather than you guessing them — point every wheel forward, run the self-test, and it reads and stores the offset. + - **Track width, wheelbase, and Pigeon 2 CAN ID.** + - Motor/encoder **inverts**, usually determined the same way — verified with the self-test rather than trial and error. + +For detailed walkthrough steps and screenshots, follow CTRE's documentation: [Creating your Project](https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-swerve/creating-your-project.html){target=_blank}. + +!!! tip "TunerConstants only, vs. a full project" + The generator can output just `TunerConstants.java`, or a full project that also includes `CommandSwerveDrivetrain.java`. Once you've customized `CommandSwerveDrivetrain` (e.g. added vision integration), **re-run the generator in "TunerConstants only" mode** when you re-measure your robot — that updates your constants without overwriting the subsystem code you've since edited. + +*** + +## What the Generator Gives You + +The generated `generated/` package (under `src/main/java/frc/robot/generated/`) contains two files: + +- **`TunerConstants.java`** — every hardware constant: CAN bus name, per-module CAN IDs/offsets/positions, gear ratios, wheel radius, and default PID/feedforward gains. It also exposes a `createDrivetrain()` factory method. +- **`CommandSwerveDrivetrain.java`** — a `Subsystem` (via `TunerSwerveDrivetrain`) that wraps Phoenix 6's `SwerveDrivetrain` for command-based use, exposing `applyRequest(...)`, `getState()`, `seedFieldCentric()`, and SysId characterization commands. + +Here's an excerpt of what a generated `TunerConstants.java` looks like, so you recognize the shape when you open the real one — **don't hand-type this**, the generator writes the actual values from your robot: + +```java title="TunerConstants.java (illustrative excerpt — generator output, not hand-written)" +--8<-- "docs/code_examples/ctre_swerve/TunerConstants.java:canbus" + +--8<-- "docs/code_examples/ctre_swerve/TunerConstants.java:module-constants" +``` + +```java title="TunerConstants.java - createDrivetrain()" +--8<-- "docs/code_examples/ctre_swerve/TunerConstants.java:create-drivetrain" +``` + +!!! note "Rotations, not degrees" + Notice `kFrontLeftEncoderOffset` is expressed in **rotations** (`Angle`/`Rotations.of(...)`), not degrees. If you've also read the YAGSL tutorial, don't carry that library's degree-based `absoluteEncoderOffset` convention over here — the two systems measure the same physical thing in different units. + +For the full generated-file reference, see CTRE's [Swerve Builder API](https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/mechanisms/swerve/swerve-builder-api.html){target=_blank} docs. + +*** + +## Wiring It Into a Command-Based Robot + +The CTRE generator assumes a freshly created WPILib project. If you need to integrate it into an existing project you can drop the generated code into `RobotContainer` alongside your other subsystems and commands. + +1. Copy the generator's `generated/` package (and `CommandSwerveDrivetrain.java`, if you generated the full project) into your existing project's `src/main/java/frc/robot/`. +2. Instantiate the drivetrain once from `TunerConstants.createDrivetrain()`. +3. Build a `SwerveRequest.FieldCentric` request and bind it as the drivetrain's default command, reading joystick axes. + +```java title="RobotContainer.java - Fields" +--8<-- "docs/code_examples/ctre_swerve/RobotContainer.java:fields" +``` + +```java title="RobotContainer.java - configureBindings()" +--8<-- "docs/code_examples/ctre_swerve/RobotContainer.java:configure-bindings" +``` + +!!! note "Why negate the joystick axes?" + Same reason as every other drive code on this site: standard joysticks report negative Y when pushed forward, so `-driverXbox.getLeftY()` corrects it so "forward" on the stick means forward on the field. + +*** + +## Driving Concepts: `SwerveRequest` + +Instead of calling a `drive(...)` method directly like YAGSL's `SwerveSubsystem`, CTRE's generated drivetrain is driven by handing it a **`SwerveRequest`** — a small, reusable, mutable object describing "what should the drivetrain do right now." You build one request per behavior, then continuously re-apply it (usually via `applyRequest(() -> request)` as a default or triggered command) with fresh values each loop. + +The handful of request types you'll actually use day-to-day: + +- **`SwerveRequest.FieldCentric`** — drive relative to the field (the normal teleop mode). +- **`SwerveRequest.RobotCentric`** — drive relative to the robot's current facing. +- **`SwerveRequest.SwerveDriveBrake`** — lock the wheels in an X to resist being pushed. +- **`SwerveRequest.PointWheelsAt`** — point every wheel at a given angle without driving (diagnostics, defense). +- **`SwerveRequest.Idle`** — do nothing; useful bound to the disabled trigger so neutral mode still applies. + +The full request catalog (including `FieldCentricFacingAngle`, `RobotCentricFacingAngle`, and Pro-only requests) is documented in CTRE's [Swerve Requests](https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/mechanisms/swerve/swerve-requests.html){target=_blank} reference — read it once you're comfortable with the pattern above, since most of it is variations on the same idea. + +*** + +## Tuning & Troubleshooting + +!!! abstract "Practical notes" + - **PID/feedforward gains** live in `TunerConstants` as `Slot0Configs` for the drive and steer motors — tune these the same way you'd tune any Phoenix 6 closed-loop controller. + - **CANcoder offsets are measured, not guessed.** Re-run the generator's self-test (or Tuner X's self-test tool directly) any time you re-mount a module, rather than hand-editing the offset. + - **CAN bus name mismatches** are the most common first-run failure: every device (TalonFX, CANcoder, Pigeon 2) must agree on whether it's on `"rio"` or your CANivore's specific name. `TunerConstants.kCANBus` sets this in one place — check it first if devices aren't responding. + - **Inverted drive/wrong module ordering** shows up as the robot driving diagonally or spinning instead of translating — re-verify CAN IDs and invert flags against what the self-test reported, rather than guessing new values. + +CTRE's own tuning guidance (Slot0 gain tuning, current limits, closed-loop output types) is more complete and versioned to the current season, so treat it as the source of truth: [Swerve System Requirements](https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-swerve/swerve-system-requirements.html){target=_blank} and the [Swerve Overview](https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/mechanisms/swerve/swerve-overview.html){target=_blank}. + +*** + +## Links to Relevant Documentation + +- **Swerve Project Generator**: [Tuner X Swerve docs](https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-swerve/){target=_blank} +- **Creating your Project (step-by-step)**: [CTRE docs](https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-swerve/creating-your-project.html){target=_blank} +- **System Requirements**: [CTRE docs](https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-swerve/swerve-system-requirements.html){target=_blank} +- **Swerve Requests API**: [CTRE docs](https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/mechanisms/swerve/swerve-requests.html){target=_blank} +- **Swerve Overview**: [CTRE docs](https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/mechanisms/swerve/swerve-overview.html){target=_blank} +- **Full working examples**: [Phoenix6-Examples repository](https://github.com/CrossTheRoadElec/Phoenix6-Examples){target=_blank} (see `SwerveWithPathPlanner` and `SwerveWithChoreo` for complete generated projects) +- **Alternative approach**: [YAGSL Swerve Tutorial](yagsl_swerve_tutorial.md) — a mixed-hardware, JSON-config alternative to this generator + +*** + +## Knowledge Check + + + + +Your team's swerve drivetrain uses only TalonFX motors, CANcoders, and a Pigeon 2. Based on this page, what's the main advantage of using CTRE's Swerve Project Generator instead of YAGSL? +- [ ] It supports mixed hardware from different vendors +- [x] It generates real Phoenix 6 code with direct access to advanced CTRE features, since you're not going through a hardware-abstraction layer +- [ ] It doesn't require any CAN bus configuration +- [ ] It works with REV SparkMax controllers as well + +Because the generator only targets CTRE hardware, it can write code directly against the Phoenix 6 API instead of going through a generic abstraction layer — giving you access to features like advanced closed-loop control and CAN FD odometry rates that a mixed-hardware library like YAGSL doesn't expose. + + + +In the generated `TunerConstants.java`, what does calling `TunerConstants.createDrivetrain()` do? +- [ ] It opens Tuner X so you can re-run the generator +- [x] It constructs a `CommandSwerveDrivetrain` from the generated drivetrain-wide and per-module constants +- [ ] It resets all CANcoder offsets to zero +- [ ] It creates a new RobotContainer + +`createDrivetrain()` is a factory method the generator writes for you that instantiates `CommandSwerveDrivetrain` using `DrivetrainConstants` and the four generated `SwerveModuleConstants` (FrontLeft, FrontRight, BackLeft, BackRight). It's meant to be called once, typically to initialize a field in `RobotContainer`. + + + +What is a `SwerveRequest` in the CTRE swerve API? +- [ ] A one-time command that drives the robot for exactly one second +- [ ] A JSON file describing the drivetrain's hardware +- [x] A reusable, mutable object describing what the drivetrain should do right now, which you continuously re-apply via `applyRequest(...)` +- [ ] A network request sent to the Driver Station + +Rather than calling a `drive(...)` method with fresh arguments, CTRE's generated drivetrain is driven by handing it a `SwerveRequest` object (like `FieldCentric` or `SwerveDriveBrake`) whose fields you update each loop, typically via `drivetrain.applyRequest(() -> request.withVelocityX(...)...)` bound as a default or triggered command. + + + +A generated `TunerConstants.java` expresses `kFrontLeftEncoderOffset` as `Rotations.of(0.152...)`. What unit does YAGSL's `absoluteEncoderOffset` use for the equivalent value, per the YAGSL tutorial on this site? +- [ ] Rotations, same as CTRE +- [ ] Radians +- [x] Degrees +- [ ] Encoder counts + +The two systems measure the same physical quantity (how far the wheel's absolute encoder reading is from "pointing forward") but in different units — CTRE's generated code uses rotations, while YAGSL's JSON config uses degrees. Mixing them up is a common source of "my wheels are all facing the wrong way" bugs when switching between the two approaches. + + + diff --git a/docs/programming/driving_robot.md b/docs/programming/driving_robot.md index 8ffd1c9..ffbc651 100644 --- a/docs/programming/driving_robot.md +++ b/docs/programming/driving_robot.md @@ -105,55 +105,6 @@ In the Drivetrain class we will tell the subsystem what type of components it wi 4. Your error should be gone! -### Creating and filling the constructor - -Now that we have created the motor variables and the Drive Constants we must initialize them and tell them what port on the roboRIO they are on. - -**1)** Initialize (set value of) the motor variables with the appropriate motor controller constructor. - -=== "SparkMax" - Initialize `leftLeader` to `new SparkMax(LEFT_LEADER_ID, MotorType.kBrushless)`. - - - This initializes a new SparkMax, `leftLeader`, in a new piece of memory and states it is on the port defined by `LEFT_LEADER_ID`. - - The constructor `SparkMax(int, MotorType)` takes a variable of type `int` for the CAN ID and `MotorType` for brushless or brushed. - -=== "TalonFX" - Initialize `leftLeader` to `new TalonFX(LEFT_LEADER_ID)`. - - - This initializes a new TalonFX, `leftLeader`, in a new piece of memory and states it is on the port defined by `LEFT_LEADER_ID`. - - Unlike SparkMax, TalonFX takes only the CAN ID — no motor type parameter is needed (TalonFX is always brushless). - -- This should be done within the constructor `CANDriveSubsystem()` - -!!! note "Java concept: Constructor" - The constructor is a special method that runs once when an object is created with `new`. It is where hardware gets initialized — motors receive their port numbers, controllers get configured. The constructor name always matches the class name exactly. See [Java Classes for more information](../basics/java_classes.md#constructors). - - **roboRIO port diagram** - -![](../assets/images/driving_robot/roboRIO_port.png) - - -??? example "Constructor Initialization Example" - ```java title="CANDriveSubsystem.java - Constructor Declaration" - public CANDriveSubsystem() {} - ``` - - **Full Constructor:** - - === "SparkMax" - ```java title="Full Constructor" - --8<-- "docs/code_examples/2026KitBotInline/subsystems/CANDriveSubsystem.java:constructor" - ``` - - See [CANDriveSubsystem.java](../code_examples/2026KitBotInline/subsystems/CANDriveSubsystem.java) for the complete constructor implementation. - - === "TalonFX" - ```java title="Full Constructor" - --8<-- "docs/code_examples/2026KitBotInlineTalonFX/subsystems/CANDriveSubsystem.java:constructor" - ``` - - See [CANDriveSubsystem.java](../code_examples/2026KitBotInlineTalonFX/subsystems/CANDriveSubsystem.java) for the complete constructor implementation. - ### Using Constants !!! note @@ -164,13 +115,15 @@ Since each subsystem has its own components with their own ports, it is easy to - Names should follow the pattern SUBSYSTEM_NAME_OF_COMPONENT - The name is all caps since it is a **constant** ([more info on constants](../basics/java_types_variables.md#constants){target=_blank}). +Before we initialize the motor objects we are going to create constants to hold the CAN IDs of the motors, plus a current limit shared by all four. This happens in `Constants.java`. + !!! note + The motor current limit is a safety feature that prevents the motors from drawing too much current and tripping breakers or damaging the motors. The limit is set in amperes (A) and should be chosen based on the motor's specifications and your robot's electrical system. This limit should always be the same or lower than the breaker rating for the motor's circuit to avoid nuisance trips. -Before we initalize the SparkMax objects we are going to create constants to hold the CAN ID's of the motors. This will happen in constants.java - -- Inside the constants class, create a new class called `public static DriveConstants`. -- Inside `DriveConstants` class, create for constants called `LEFT_LEADER_ID`, `LEFT_FOLLOWER_ID`, `RIGHT_LEADER_ID`, and `RIGHT_FOLLOWER_ID`. -- Back in your `DriveTrain` class in `drivetrain.java`, import the `DriveConstants` class as follows: `Import frc.robot.Constants.DriveConstants;`. +- Inside the `Constants` class, create a new inner class called `public static final class DriveConstants`. +- Inside `DriveConstants`, create four constants: `LEFT_LEADER_ID`, `LEFT_FOLLOWER_ID`, `RIGHT_LEADER_ID`, and `RIGHT_FOLLOWER_ID`. +- Add a fifth constant, `DRIVE_MOTOR_CURRENT_LIMIT`, set to a reasonable amperage. This caps how much current each drive motor can draw so you don't trip breakers or damage a motor. +- Back in `CANDriveSubsystem.java`, add a **static import** for `DriveConstants` so its members can be referenced without the `DriveConstants.` prefix: `import static frc.robot.Constants.DriveConstants.*;` !!! tip Make sure to declare constants with `public static final` so they cannot be changed at runtime. @@ -178,7 +131,7 @@ Before we initalize the SparkMax objects we are going to create constants to hol !!! danger If you set this to the wrong value, you could damage your robot when it tries to move! !!! note - To use Constants, instead of putting `0` for the port in the SparkMax type: + To use Constants, instead of putting `0` for the port in the motor constructor: ```java title="Constants.java - Drive Constants" public static final int LEFT_LEADER_ID = 1; @@ -188,7 +141,7 @@ Before we initalize the SparkMax objects we are going to create constants to hol Replace the remaining numbers with constants. !!! tip - Remember to save both **Drivetrain.java** and **Constants.java** + Remember to save both **CANDriveSubsystem.java** and **Constants.java** ??? example "DriveConstants Example" **Drive Constants Definition:** @@ -214,7 +167,34 @@ Before we initalize the SparkMax objects we are going to create constants to hol !!! warning Remember to use the values for **YOUR** specific robot or you could risk damaging it! -### Configuring Motor Controllers +### Creating and filling the constructor + +Now that we have created the motor variables and the Drive Constants we must initialize them and tell them what port on the roboRIO they are on. + +**1)** Initialize (set value of) the motor variables with the appropriate motor controller constructor. + +=== "SparkMax" + Initialize `leftLeader` to `new SparkMax(LEFT_LEADER_ID, MotorType.kBrushless)`. + + - This initializes a new SparkMax, `leftLeader`, in a new piece of memory and states it is on the port defined by `LEFT_LEADER_ID`. + - The constructor `SparkMax(int, MotorType)` takes a variable of type `int` for the CAN ID and `MotorType` for brushless or brushed. + +=== "TalonFX" + Initialize `leftLeader` to `new TalonFX(LEFT_LEADER_ID)`. + + - This initializes a new TalonFX, `leftLeader`, in a new piece of memory and states it is on the port defined by `LEFT_LEADER_ID`. + - Unlike SparkMax, TalonFX takes only the CAN ID — no motor type parameter is needed (TalonFX is always brushless). + +- This should be done within the constructor `CANDriveSubsystem()`, for all four motors (`leftLeader`, `leftFollower`, `rightLeader`, `rightFollower`). + +!!! note "Java concept: Constructor" + The constructor is a special method that runs once when an object is created with `new`. It is where hardware gets initialized — motors receive their port numbers, controllers get configured. The constructor name always matches the class name exactly. See [Java Classes for more information](../basics/java_classes.md#constructors). + + **roboRIO port diagram** + +![](../assets/images/driving_robot/roboRIO_port.png) + +**2)** Configure CAN communication and current limits. **Setting CAN Communication Timeout:** @@ -229,9 +209,9 @@ Before we initalize the SparkMax objects we are going to create constants to hol !!! note "No CAN timeout needed" Phoenix 6 manages CAN communication internally. No explicit `setCANTimeout()` equivalent exists. Configuration is applied via `getConfigurator().apply(config)` — see the section below. -**Voltage Compensation and Current Limiting:** +**Voltage Compensation, Current Limiting, and Neutral Mode:** -Create the configuration to apply to motors. The current limit helps prevent tripping breakers. +Create the configuration to apply to motors. `DRIVE_MOTOR_CURRENT_LIMIT` (from `DriveConstants`) sets the current limit, which helps prevent tripping breakers. === "SparkMax" Voltage compensation helps the robot perform more similarly on different battery voltages (at the cost of a little bit of top speed on a fully charged battery). @@ -247,26 +227,10 @@ Create the configuration to apply to motors. The current limit helps prevent tri --8<-- "docs/code_examples/2026KitBotInlineTalonFX/subsystems/CANDriveSubsystem.java:voltage-compensation" ``` -=== "SparkMax" - See [CANDriveSubsystem.java](../code_examples/2026KitBotInline/subsystems/CANDriveSubsystem.java) for the full configuration implementation in the constructor. - -=== "TalonFX" - See [CANDriveSubsystem.java](../code_examples/2026KitBotInlineTalonFX/subsystems/CANDriveSubsystem.java) for the full configuration implementation in the constructor. - -## Creating the arcade drive - -### What is the Drive Class - -- The FIRST Drive class has many pre-configured methods available to us including DifferentialDrive, and many alterations of MecanumDrive. -- DifferentialDrive contains subsections such as TankDrive and ArcadeDrive. - For more information and details on drive bases see the [WPILib documentation](https://docs.wpilib.org/en/stable/docs/software/hardware-apis/motors/wpi-drive-classes.html){target=_blank}. -- For our tutorial we will be creating an ArcadeDrive -- Arcade drives run by taking a moveSpeed and rotateSpeed. moveSpeed defines the forward and reverse speed and rotateSpeed defines the turning left and right speed. -- To create an arcade drive we will be using our already existing Drivetrain class and adding to it. - -### Programing a RobotDrive + !!! note + This configuration also sets `NeutralMode` to `Brake`. This tells the motor to actively resist being pushed when no power is commanded — useful for a drivetrain, since you generally don't want the robot to coast or roll after you let go of the joystick. The SparkMax equivalent is `setIdleMode(IdleMode.kBrake)`. -**1)** Create the DifferentialDrive object. +**3)** Declare and initialize the `DifferentialDrive` object using the two leader motors. **Member Variable Declaration:** @@ -292,7 +256,6 @@ This defines the drive object that we will use to drive the robot. - Since DifferentialDrive takes 2 parameters we pass the left and right leader motors. - SparkMax implements the WPILib `MotorController` interface, so it can be passed directly to `DifferentialDrive`. - - The follower motors are configured separately (see below) to follow the leaders. === "TalonFX" ```java title="CANDriveSubsystem.java - Drive Initialization" @@ -302,14 +265,38 @@ This defines the drive object that we will use to drive the robot. - TalonFX (Phoenix 6) does **not** implement the WPILib `MotorController` interface, so a `TalonFX` object cannot be passed directly to the `MotorController`-based constructor. - TalonFX does expose a `set(double)` method, so we use `DifferentialDrive`'s other constructor, which takes two `DoubleConsumer` method references (`leftLeader::set`, `rightLeader::set`) instead of `MotorController` objects. See CTRE's [MotorController Integration](https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/wpilib-integration/motorcontroller-integration.html){target=_blank} docs. - - The follower motors are configured separately (see below) to follow the leaders. + +!!! note "Why only the leaders?" + `DifferentialDrive` only needs to know about the two leader motors. The followers are wired up separately in the next section using each vendor's follower-control feature, so they automatically mirror their leader's output without `DifferentialDrive` needing to touch them directly. + +!!! success "Build & Verify" + Build your code now (**Ctrl+Shift+P → WPILib: Build Robot Code**) and confirm there are no compile errors before moving on. At this point your constructor creates all four motors, builds the current-limit configuration, and initializes `drive` with the two leaders — the followers aren't wired up yet, so hold off on deploying to a robot until the next section is done. + +??? example "Constructor So Far" + === "SparkMax" + See [CANDriveSubsystem.java](../code_examples/2026KitBotInline/subsystems/CANDriveSubsystem.java) for the complete constructor implementation, including the follower setup covered in the next section. + + === "TalonFX" + See [CANDriveSubsystem.java](../code_examples/2026KitBotInlineTalonFX/subsystems/CANDriveSubsystem.java) for the complete constructor implementation, including the follower setup covered in the next section. + +## Creating the arcade drive + +### What is the Drive Class + +- The FIRST Drive class has many pre-configured methods available to us including DifferentialDrive, and many alterations of MecanumDrive. +- DifferentialDrive contains subsections such as TankDrive and ArcadeDrive. + For more information and details on drive bases see the [WPILib documentation](https://docs.wpilib.org/en/stable/docs/software/hardware-apis/motors/wpi-drive-classes.html){target=_blank}. +- For our tutorial we will be creating an ArcadeDrive +- Arcade drives run by taking a moveSpeed and rotateSpeed. moveSpeed defines the forward and reverse speed and rotateSpeed defines the turning left and right speed. +- We already created and initialized the `drive` object in the constructor — now we'll wire up the follower motors and use `drive` to build a command the driver can control. + +### Configuring Followers and Motor Direction + +In order to configure the motors to drive correctly, we need to configure one on each side as the leader and one as the follower. In the constructor, after building the current-limit configuration, we set the follower motors and link them to the leader motors. !!! warning You should only group motors that are spinning the same direction physically when positive power is being applied otherwise you could damage your robot. -**2)** In order to configure the motors to drive correctly, we need to configure one on each side as the leader and one as the follower. -In the constructor we are going to set the follower motors and link them to the leader motors. - === "SparkMax" To do this we will need to include classes from the REV Library: ```java title="CANDriveSubsystem.java - Required Imports" @@ -398,6 +385,14 @@ Instead of writing a separate command class, we use the **command factory patter !!! tip Multiply the speed values by a decimal to cap the max speed during initial testing (e.g. `xSpeed.getAsDouble() * 0.5`). This makes it easier to verify the robot drives in the correct directions before running at full power/speed. +!!! bug "Common Issues" + - **Robot drives backward when you push forward** — the left or right side inversion is flipped. Double-check which side you inverted and try the opposite value. + - **Followers don't move** — verify the CAN ID passed to the follower setup matches the leader's actual CAN ID constant, not a hardcoded number. + - **Cannot find symbol `Follower` / `InvertedValue`** (TalonFX) — make sure the Phoenix 6 imports listed above are present; VSCode's Quick Fix can add them automatically. + +!!! success "Build & Verify" + Build the project again (**Ctrl+Shift+P → WPILib: Build Robot Code**) and confirm there are no errors. If you have a robot connected, this is a good point to deploy and bench-test: push the left stick forward/back to drive, and the right stick left/right to rotate. + ## Wiring Up in RobotContainer Now we connect the subsystem to the driver’s controller by setting a default command in `RobotContainer.java`. @@ -440,6 +435,10 @@ The `RobotContainer` class holds all subsystems, controllers, and command bindin Lambdas are required here because `driveArcade` expects `DoubleSupplier` parameters. A lambda `() -> driverController.getLeftY()` is a `DoubleSupplier` — it gets called every loop cycle so the robot continuously responds to joystick movement. +!!! bug "Common Issues" + - **Robot doesn't respond to joystick** — confirm your Xbox controller shows up in the Driver Station's USB tab at the port number stored in `DRIVER_CONTROLLER_PORT`. + - **`setDefaultCommand` never seems to run** — make sure the call is inside `configureBindings()`, and that `configureBindings()` is actually called from the `RobotContainer` constructor. + *** ## Knowledge Check @@ -503,4 +502,15 @@ What does `setDefaultCommand` do? See [RobotContainer.java](../code_examples/2026KitBotInline/RobotContainer.java) for the complete `RobotContainer` implementation. === "TalonFX" - See [RobotContainer.java](../code_examples/2026KitBotInlineTalonFX/RobotContainer.java) for the complete `RobotContainer` implementation. \ No newline at end of file + See [RobotContainer.java](../code_examples/2026KitBotInlineTalonFX/RobotContainer.java) for the complete `RobotContainer` implementation. + +*** + +## Next Steps + +Your drivetrain is now functional! Consider extending it: + +- **Add autonomous routines** — see [Autonomous](autonomous.md) for building commands that run without driver input. +- **Add odometry** — track the robot's position on the field using encoder data. +- **Add smoothing** — filter joystick input (e.g. with a slew rate limiter) to reduce jitter and jerky driving. +- **Tune scaling factors** — revisit `DRIVE_SCALING` and `ROTATION_SCALING` in `Constants.java`, and see [PID Control](pid.md) if you want to drive to a specific distance or angle automatically. diff --git a/docs/programming/yagsl_swerve_tutorial.md b/docs/programming/yagsl_swerve_tutorial.md index 97d55ce..a7e2216 100644 --- a/docs/programming/yagsl_swerve_tutorial.md +++ b/docs/programming/yagsl_swerve_tutorial.md @@ -15,6 +15,16 @@ YAGSL (Yet Another Generic Swerve Library) is a swerve drive library developed b ### Why YAGSL? Unlike many swerve templates that require extensive modification, YAGSL abstracts hardware differences, so teams can focus on robot logic rather than drive code. It's particularly useful for teams with multiple robots or those using non-standard hardware combinations. +!!! tip "Two hardware paths, one tutorial" + This tutorial walks through both of the most common FRC swerve hardware combinations side-by-side, using tabs like the ones below wherever the configuration differs: + + - **REVLib**: REV SparkMax controllers driving NEO motors, with a CTRE CANcoder for absolute position. + - **TalonFX**: CTRE TalonFX controllers driving Kraken X60 / Falcon 500 motors, with a CTRE CANcoder and Pigeon 2 IMU. + + Every Java class YAGSL gives you (`SwerveSubsystem`, drive commands, odometry, etc.) is **identical** regardless of which hardware you use — YAGSL abstracts that difference away entirely into the JSON configuration files. The only thing that ever changes in your Java code is which deploy folder you point YAGSL at. + + If you're using all-CTRE hardware and want CTRE's own first-party generator instead of YAGSL's JSON abstraction, see the [CTRE Swerve Project Generator tutorial](ctre_swerve_generator_tutorial.md) for the alternative approach. + For more details, see the [YAGSL Overview](https://docs.yagsl.com/overview/what-we-do). ## 2. Prerequisites and Dependencies @@ -38,10 +48,13 @@ YAGSL requires vendor libraries for all supported hardware, even if not used on - **PhotonVision** (optional, for vision): `https://maven.photonvision.org/repository/internal/org/photonvision/PhotonLib-json/1.0/PhotonLib-json-1.0.json` - **YAGSL**: `https://yet-another-software-suite.github.io/YAGSL/yagsl.json` +!!! note "You only strictly need the vendordep for your own hardware" + YAGSL's JSON parser only touches the vendor library for the hardware types referenced in your config files. Installing REVLib (NEO) and Phoenix 6 (CTRE) side by side is harmless (and required if you ever mix hardware), but if your robot is purely CTRE (TalonFX) based, for example, you don't need to worry about configuring REV devices in the REV Hardware Client. + Installation steps: [3rd Party Libraries](https://docs.wpilib.org/en/stable/docs/software/vscode-overview/3rd-party-libraries.html#installing-libraries) ### Hardware Knowledge -You should know your robot's physical characteristics before configuration. See section 3 for details. +You should know your robot's physical characteristics while configuring YAGSL. (See section 3 below for details). ## 3. Hardware Requirements and Getting to Know Your Robot @@ -56,21 +69,21 @@ A swerve drive consists of: ### Pre-Configuration Checklist Before configuring YAGSL, gather these details about your robot: -- **IMU Type and ID**: What gyroscope are you using and its CAN ID? +- **IMU Type and ID**: What gyroscope are you using and what is its CAN ID? - **Module Configuration**: For each swerve module: - - Drive motor type, CAN ID, and gearing - - Angle motor type, CAN ID, and gearing - - Encoder type, CAN ID, and mounting offset + - Drive motor type, CAN ID, and canbus name (if using multiple CAN buses) + - Angle motor type, CAN ID, and canbus name. + - Encoder type, CAN ID. - Physical location relative to robot center (X, Y coordinates in inches) - **Physical Properties**: - Wheel diameter - - Drive gear ratio (motor rotations per wheel rotation) + - Drive gear ratio (motor rotations per wheel rotation) **this can be found on the swerve module spec sheet for your specific module** - Angle gear ratio (motor rotations per 360° module rotation) - - Robot track width and wheelbase - - Maximum speed (feet per second) -- **CAN Bus Configuration**: Ensure all devices have unique IDs and proper termination + - Robot coefficient of friction (for feedforward calculations) (optional, can be estimated) + - Maximum speed (feet per second) (Generally, this is the free speed of your drive motors divided by the drive gear ratio, multiplied by wheel circumference) +- **CAN Bus Configuration**: Ensure all devices have unique IDs. -For a complete list, see [Getting to Know Your Robot](https://docs.yagsl.com/configuring-yagsl/getting-to-know-your-robot). +For a complete list and more detailed explanations, see [Getting to Know Your Robot](https://docs.yagsl.com/configuring-yagsl/getting-to-know-your-robot). ## 4. Configuration Steps (JSON Files, Module Setup) @@ -99,9 +112,9 @@ This file defines the overall swerve drive configuration, including the IMU (gyr !!! abstract "Key Properties" - `imu`: Configures the gyroscope/IMU used for heading tracking - - `type`: The type of IMU ("pigeon2", "navx", "adxrs450", etc.) + - `type`: The type of IMU ("pigeon2", "navx", etc.) - `id`: CAN ID of the IMU device - - `canbus`: CAN bus name (usually "rio" for roboRIO bus) + - `canbus`: CAN bus name (usually "rio" for roboRIO bus, or your CANivore's name) - `invertedIMU`: Whether to invert the IMU reading (used for orientation correction) - `modules`: Array of module configuration file names @@ -141,10 +154,10 @@ This file defines the overall swerve drive configuration, including the IMU (gyr } ``` -**Complete swervedrive.json Example:** -```json title="swervedrive.json - Complete Example from swerve/neo" ---8<-- "docs/code_examples/swerve/neo/swervedrive.json" -``` +!!! tip "IMU choice is independent of motor vendor" + A Pigeon2 works fine on a SparkMax/NEO robot, and a NavX works fine on a TalonFX robot — the IMU is a separate hardware choice from your drive/angle motor controllers. The complete examples below use a Pigeon2 for both hardware sets to keep the comparison focused on the motor/encoder differences. + + #### Module JSON Files - Individual Swerve Module Configuration @@ -152,40 +165,70 @@ Each swerve module (wheel) has its own configuration file defining the drive mot !!! abstract "Key Properties" - `drive`: Configuration for the drive (translation) motor - - `type`: Motor controller type ("sparkmax", "talonfx", "talonsrx", etc.) + - `type`: Motor controller type — e.g. `"sparkmax_neo"` for a SparkMax driving a NEO, `"talonfx"` for a TalonFX driving a Kraken X60/Falcon 500 - `id`: CAN ID of the motor controller - `canbus`: CAN bus name - - `angle`: Configuration for the angle (steering) motor + - `angle`: Configuration for the angle (steering) motor, same `type` options as `drive` - `encoder`: Configuration for the absolute encoder - - `type`: Encoder type ("cancoder", "analog", "thrifty", etc.) + - `type`: Encoder type ("cancoder", "canandmag", "thrifty", "throughbore", etc.) - `inverted`: Motor inversion settings - `drive`: Whether to invert drive motor direction - `angle`: Whether to invert angle motor direction - - `absoluteEncoderInverted`: Whether to invert encoder reading - - `absoluteEncoderOffset`: Encoder offset in rotations (0.0 to 1.0) + - `absoluteEncoderOffset`: Encoder offset **in degrees** from 0°. May be negative. - `location`: Physical location relative to robot center - `front`: Distance forward from center (inches) - `left`: Distance left from center (inches, negative for right side) -**Example - SparkMax NEO with CANCoder (Front-Left):** -```json title="frontleft.json - SparkMax NEO with CANCoder" ---8<-- "docs/code_examples/swerve/neo/modules/frontleft.json" -``` +!!! note "Measuring absoluteEncoderOffset" + Point every wheel straight forward, read each CANcoder/encoder's raw position, and set `absoluteEncoderOffset` to the negative of that reading (in degrees) so the module reports 0° when facing forward. + +**Example - Front-Left Module (frontleft.json):** + +=== "SparkMax" + ```json title="frontleft.json - SparkMax NEO with CANCoder" + --8<-- "docs/code_examples/swerve/neo/modules/frontleft.json" + ``` + +=== "TalonFX" + ```json title="frontleft.json - TalonFX with CANcoder" + --8<-- "docs/code_examples/swerve/talonfx/modules/frontleft.json" + ``` **Example - Front-Right Module (frontright.json):** -```json title="frontright.json" ---8<-- "docs/code_examples/swerve/neo/modules/frontright.json" -``` -**Example - Back-Right Module (backright.json):** -```json title="backright.json" ---8<-- "docs/code_examples/swerve/neo/modules/backright.json" -``` +=== "SparkMax" + ```json title="frontright.json" + --8<-- "docs/code_examples/swerve/neo/modules/frontright.json" + ``` + +=== "TalonFX" + ```json title="frontright.json" + --8<-- "docs/code_examples/swerve/talonfx/modules/frontright.json" + ``` **Example - Back-Left Module (backleft.json):** -```json title="backleft.json" ---8<-- "docs/code_examples/swerve/neo/modules/backleft.json" -``` + +=== "SparkMax" + ```json title="backleft.json" + --8<-- "docs/code_examples/swerve/neo/modules/backleft.json" + ``` + +=== "TalonFX" + ```json title="backleft.json" + --8<-- "docs/code_examples/swerve/talonfx/modules/backleft.json" + ``` + +**Example - Back-Right Module (backright.json):** + +=== "SparkMax" + ```json title="backright.json" + --8<-- "docs/code_examples/swerve/neo/modules/backright.json" + ``` + +=== "TalonFX" + ```json title="backright.json" + --8<-- "docs/code_examples/swerve/talonfx/modules/backright.json" + ``` #### physicalproperties.json - Physical Robot Parameters @@ -193,34 +236,26 @@ This file defines the physical characteristics of your robot and swerve modules !!! abstract "Key Properties" - `optimalVoltage`: Battery voltage for calculations (usually 12.0V) - - `wheelDiameter`: Diameter of drive wheels in inches - - `driveGearRatio`: Gear ratio from motor to wheel (motor rotations per wheel rotation) - - `angleGearRatio`: Gear ratio from motor to module rotation (motor rotations per 360° module turn) + - `conversionFactors.drive.diameter`: Diameter of drive wheels in inches + - `conversionFactors.drive.gearRatio`: Gear ratio from motor to wheel (motor rotations per wheel rotation) + - `conversionFactors.angle.gearRatio`: Gear ratio from motor to module rotation (motor rotations per 360° module turn) + - `currentLimit`: Maximum current for each motor in amps (protects from stalling) + - `rampRate`: How quickly motors accelerate (0.0-1.0; lower = slower acceleration) -**Example - 4-inch wheels with 6.75:1 drive ratio:** -```json title="physicalproperties.json - 4-inch Wheels" -{ - "optimalVoltage": 12.0, - "wheelDiameter": 4.0, - "driveGearRatio": 6.75, - "angleGearRatio": 12.8 -} -``` +**Complete physicalproperties.json Example:** -**Example - 3-inch wheels with 8.14:1 drive ratio:** -```json title="physicalproperties.json - 3-inch Wheels" -{ - "optimalVoltage": 12.0, - "wheelDiameter": 3.0, - "driveGearRatio": 8.14, - "angleGearRatio": 12.8 -} -``` +=== "SparkMax" + ```json title="physicalproperties.json - Complete Example from swerve/neo" + --8<-- "docs/code_examples/swerve/neo/modules/physicalproperties.json" + ``` -**Complete physicalproperties.json Example:** -```json title="physicalproperties.json - Complete Example from swerve/neo" ---8<-- "docs/code_examples/swerve/neo/modules/physicalproperties.json" -``` +=== "TalonFX" + ```json title="physicalproperties.json - Complete Example from swerve/talonfx" + --8<-- "docs/code_examples/swerve/talonfx/modules/physicalproperties.json" + ``` + +!!! note "Current limits differ by hardware" + SparkMax uses a single smart current limit per motor. TalonFX has more current-limit options (supply vs. stator current) but YAGSL's `currentLimit` field maps to a sane default for either — Kraken X60 drive motors can typically handle a higher limit (60A above) than a NEO (40A) before you risk browning out. #### pidfproperties.json - Motor Control Tuning @@ -235,50 +270,43 @@ This file contains PIDF (Proportional, Integral, Derivative, Feedforward) tuning - `iz`: Integral zone (error threshold for integral accumulation) - `angle`: PIDF values for angle motors (steering) -**Example - SparkMax tuning values:** -```json title="pidfproperties.json - SparkMax Tuning" ---8<-- "docs/code_examples/swerve/neo/modules/pidfproperties.json" -``` - -**Example - TalonFX tuning values:** -```json title="pidfproperties.json - TalonFX Tuning" -{ - "drive": { - "p": 1.0, - "i": 0.0, - "d": 0.0, - "f": 0.0, - "iz": 0.0 - }, - "angle": { - "p": 50.0, - "i": 0.0, - "d": 0.32, - "f": 0.0, - "iz": 0.0 - } -} -``` +!!! warning "These gains are not portable between vendors" + SparkMax and TalonFX use different internal units and closed-loop scaling, so a PIDF value tuned for one will not behave the same on the other. Always start from the vendor-appropriate values below and re-tune for your own robot — see [section 7](#7-tuning-and-debugging). **Complete pidfproperties.json Example:** -```json title="pidfproperties.json - Complete Example from swerve/neo" ---8<-- "docs/code_examples/swerve/neo/modules/pidfproperties.json" -``` +=== "SparkMax" + ```json title="pidfproperties.json - Complete Example from swerve/neo" + --8<-- "docs/code_examples/swerve/neo/modules/pidfproperties.json" + ``` + +=== "TalonFX" + ```json title="pidfproperties.json - Complete Example from swerve/talonfx" + --8<-- "docs/code_examples/swerve/talonfx/modules/pidfproperties.json" + ``` + +**Complete swervedrive.json Example:** + +=== "SparkMax" + ```json title="swervedrive.json - Complete Example from swerve/neo" + --8<-- "docs/code_examples/swerve/neo/swervedrive.json" + ``` + +=== "TalonFX" + ```json title="swervedrive.json - Complete Example from swerve/talonfx" + --8<-- "docs/code_examples/swerve/talonfx/swervedrive.json" + ``` + #### controllerproperties.json - Advanced Control Settings -This file configures advanced control parameters for heading correction and velocity control (usually left at defaults). +This file configures advanced control parameters for heading correction (usually left at defaults). It has just two fields, and — unlike the module/physical/PIDF files above — it does **not** vary by motor vendor, since it configures the drivetrain-level heading controller rather than any individual motor. !!! abstract "Key Properties" - - `heading`: PID values for heading correction + - `angleJoystickRadiusDeadband`: Minimum radius of the angle-control joystick input before a heading adjustment is applied + - `heading`: PID values for heading correction (used when driving with a target heading, e.g. `driveCommand(x, y, headingX, headingY)`) - `p`: Proportional gain for heading control - `i`: Integral gain - `d`: Derivative gain - - `velocity`: Velocity control PID values - - `x`: PID for X-axis velocity control - - `y`: PID for Y-axis velocity control - - **Controllerproperties.json Example:** ```json title="controllerproperties.json - Complete Example from swerve/neo" @@ -289,7 +317,7 @@ This file configures advanced control parameters for heading correction and velo YAGSL provides an online configuration generator: [YAGSL Config Tool](https://broncbotz3481.github.io/YAGSL-Example/) 1. Input your robot's physical parameters -2. Select hardware types and IDs +2. Select hardware types and IDs for each module — choose `SparkMax` (NEO/NEO 550/Vortex) or `TalonFX` (Falcon 500/Kraken X60) as appropriate per motor, and your absolute encoder type 3. Download the generated configuration files 4. Place them in `src/main/deploy/swerve/` @@ -309,6 +337,20 @@ In your subsystem constructor, initialize the swerve drive from your JSON config --8<-- "docs/code_examples/swerve/SwerveSubsystem.java:constructor" ``` +This is instantiated in `RobotContainer` by pointing a `SwerveSubsystem` at the deploy folder that matches your hardware — this deploy-folder name is the **only** line of Java that differs between the REVLib and TalonFX paths; every other class in this tutorial is identical either way: + +=== "SparkMax" + ```java + private final SwerveSubsystem drivebase = new SwerveSubsystem( + new File(Filesystem.getDeployDirectory(), "swerve/neo")); + ``` + +=== "TalonFX" + ```java + private final SwerveSubsystem drivebase = new SwerveSubsystem( + new File(Filesystem.getDeployDirectory(), "swerve/talonfx")); + ``` + ### Telemetry Setup YAGSL provides extensive telemetry for debugging. Configure verbosity before creating the SwerveDrive: ```java title="Telemetry Configuration" @@ -413,23 +455,17 @@ For more examples, see the [YAGSL Examples Repository](https://github.com/Yet-An ## 7. Tuning and Debugging ### PIDF Tuning -YAGSL uses PIDF controllers for both drive and angle motors. Start with these values: +YAGSL uses PIDF controllers for both drive and angle motors. Start with these values, then tune from there: -**SparkMax-based systems:** -```json title="pidfproperties.json - SparkMax Configuration" -{ - "drive": {"p": 0.0020645, "i": 0, "d": 0, "f": 0, "iz": 0}, - "angle": {"p": 0.01, "i": 0, "d": 0, "f": 0, "iz": 0} -} -``` +=== "SparkMax" + ```json title="pidfproperties.json - SparkMax Starting Point" + --8<-- "docs/code_examples/swerve/neo/modules/pidfproperties.json" + ``` -**TalonFX-based systems:** -```json title="pidfproperties.json - TalonFX Configuration" -{ - "drive": {"p": 1, "i": 0, "d": 0, "f": 0, "iz": 0}, - "angle": {"p": 50, "i": 0, "d": 0.32, "f": 0, "iz": 0} -} -``` +=== "TalonFX" + ```json title="pidfproperties.json - TalonFX Starting Point" + --8<-- "docs/code_examples/swerve/talonfx/modules/pidfproperties.json" + ``` Tuning process: 1. Set P, I, D, F to 0 @@ -456,10 +492,11 @@ Test after each step. Most robots work after step 1, 3, or 7. For complete details, see [When to Invert](https://docs.yagsl.com/configuring-yagsl/when-to-invert) and [The Eight Steps](https://docs.yagsl.com/configuring-yagsl/the-eight-steps). ### Common Issues -- **Modules not facing correct direction**: Check absolute encoder offsets +- **Modules not facing correct direction**: Check absolute encoder offsets (remember: degrees, not rotations) - **Robot drifting in odometry**: Verify IMU orientation and module locations - **Gears grinding**: PID tuning issue, not inversion - **Inconsistent behavior**: Ensure all modules have same hardware configuration +- **TalonFX-specific**: double-check the CAN bus name (`"rio"` vs. your CANivore's name) matches on every device — a mismatched `canbus` field is a common first-time TalonFX/CANcoder/Pigeon2 setup mistake ## 8. Links to Relevant Documentation @@ -469,6 +506,7 @@ For complete details, see [When to Invert](https://docs.yagsl.com/configuring-ya - **WPILib Swerve Kinematics**: [Swerve Drive Kinematics](https://docs.wpilib.org/en/stable/docs/software/kinematics-and-odometry/swerve-drive-kinematics.html) - **CTRE Swerve Overview**: [Phoenix 6 Swerve](https://v6.docs.ctr-electronics.com/en/stable/docs/api-reference/mechanisms/swerve/swerve-overview.html) - **REV Swerve Resources**: [REV Swerve Documentation](https://docs.revrobotics.com/brushless/neo/vortex/vortex-shafts) +- **Alternative approach**: [CTRE Swerve Project Generator Tutorial](ctre_swerve_generator_tutorial.md) — CTRE's own first-party codegen tool for all-CTRE hardware ## Additional Resources @@ -495,4 +533,24 @@ What does "holonomic" mean for a drivetrain? A holonomic drivetrain can move in any direction without first rotating to face that direction. Swerve drives are holonomic; tank drives are not. + +A team switches their swerve robot from SparkMax/NEO to TalonFX/Kraken motors. According to this tutorial, what Java code needs to change in `SwerveSubsystem.java` or `RobotContainer.java`? +- [ ] All of the drive commands need to be rewritten for TalonFX +- [ ] The `SwerveInputStream` joystick code needs vendor-specific logic +- [x] Nothing except the deploy folder name passed into the `SwerveSubsystem` constructor (e.g. `"swerve/neo"` to `"swerve/talonfx"`) +- [ ] The odometry and pose-reset methods need to be reimplemented + +YAGSL abstracts hardware differences into the JSON configuration files. Every Java class in this tutorial works identically for both vendors — the only thing that changes is which deploy folder of JSON configs you point the `SwerveSubsystem` at. + + + +What unit is `absoluteEncoderOffset` measured in in a YAGSL module JSON file? +- [ ] Rotations, from 0.0 to 1.0 +- [x] Degrees, and it may be negative +- [ ] Encoder counts +- [ ] Radians + +Despite some documentation describing it as a 0.0–1.0 rotation value, YAGSL's `absoluteEncoderOffset` is measured in degrees. You measure it by pointing the wheel forward, reading the raw encoder value, and setting the offset to the negative of that reading. + + diff --git a/improvement_analysis/classroom50-pathplanner-lesson-plan.md b/improvement_analysis/classroom50-pathplanner-lesson-plan.md new file mode 100644 index 0000000..34e60d3 --- /dev/null +++ b/improvement_analysis/classroom50-pathplanner-lesson-plan.md @@ -0,0 +1,229 @@ +# Plan: Classroom50 Lesson — "PathPlanner-Autonomous" + +## Context + +[pathplanner-tutorial-plan.md](pathplanner-tutorial-plan.md) plans two *reference* docs pages (`pathplanner_overview.md`, `pathplanner_autonomous.md`) for this tutorial site. This plan is the hands-on practice companion: a classroom50 assignment in the separate **RoboLancers-Java-Learning/management** repo, which is the org's classroom50 config/template repo (confirmed structure: `.github/workflows/setup-lesson.yml` provisions a per-student repo from a `workflow_dispatch` choice of lesson name; `shared-devcontainer/.devcontainer/` is the common Codespaces environment for all WPILib-based lessons; each lesson directory — `Basic-DriveTrain/`, `GitHub-Basics/`, `Swerve-Drive/` — is a full Gradle project with `unitN-topic.md` lesson pages, matching `src/test/java/frc/robot/UnitNXxxTest.java` JUnit graders, and (for JSON-config units) `grading/*.sh` scripts). + +This lesson assumes the student has completed the **Swerve-Drive** lesson (a working, filled-in `SwerveSubsystem` + `deploy/swerve/` JSON) — it is not re-teaching swerve config, it's teaching the PathPlanner GUI → deploy folder → Java autonomous pipeline on top of that. + +Per the user's request, this plan covers: +1. A branch in `RoboLancers-Java-Learning/management` to do the work on. +2. A `shared-devcontainer` update to install the PathPlanner GUI (verified concretely below). +3. Five units: **Creating a Path**, **Creating an Auto**, **Creating a NamedCommand**, **Adding an AutoChooser**, **Full choose-and-run sequence verified in sim**. + +Everything below was checked against the real repo content (not assumed): `shared-devcontainer/.devcontainer/{Dockerfile,post-create.sh,post-attach.sh}`, `Swerve-Drive/{unit1..5}-*.md`, `Swerve-Drive/grading/check_swerve_config.sh`, `Swerve-Drive/src/test/java/frc/robot/Unit3DriveCommandTest.java`, `Swerve-Drive/vendordeps/`, `docs/TEMPLATES.md`, `docs/CONFIGURATION.md`, and `.github/workflows/setup-lesson.yml`. The PathPlanner GUI's actual Linux release (`mjansen4857/pathplanner` v2026.1.2) was downloaded and inspected — it's a self-contained Flutter/GTK Linux bundle (top-level `pathplanner` binary + `lib/` + `data/`), **not** an Electron app, so it doesn't need the `--no-sandbox` wrapper that `AdvantageScope`/`Elastic` do. + +**`management` vs. `classroom50` — two different repos, two different jobs.** `RoboLancers-Java-Learning/management`'s `setup-lesson.yml` is a homegrown workflow that authors/refreshes **template repos** (`Basic-DriveTrain-2026`, `Swerve-Drive-2026`, etc., each flagged `is_template: true`). It is *not* classroom50 itself. The actual classroom50 config repo is the separate `RoboLancers-Java-Learning/classroom50` (public; holds `frc-java/{classroom.json,assignments.json,students.csv,scores.json}` and `autograders/`), and it's what turns a template repo into something a student can actually `gh student accept`. Steps 1–5 below build the template; Step 6 is the classroom50 registration that was missing from the first version of this plan. + +*** + +## Step 1 — Branch + +```sh +git clone https://github.com/RoboLancers-Java-Learning/management.git +cd management +git checkout -b lesson/pathplanner-autonomous +``` + +All work below happens on this branch; open a PR into `main` when done (the repo's default branch protection requires 1 approving review + passing `spotless`/`build` checks per `docs/CONFIGURATION.md` — a mentor review is expected here, not a direct merge). + +*** + +## Step 2 — Update `shared-devcontainer` to install PathPlanner + +This is shared across **all** WPILib lessons (`Basic-DriveTrain`, `Swerve-Drive`, and the new lesson), which is correct — it's a generally useful tool, not lesson-specific. + +**`shared-devcontainer/.devcontainer/post-create.sh`** — add a block immediately after the existing "Installing Elastic..." block (same pattern as `AdvantageScope`/`Elastic`, but simpler since PathPlanner's Linux bundle isn't Electron): + +```bash +echo "Installing PathPlanner..." +mkdir -p ~/wpilib/2026/pathplanner +wget -q "https://github.com/mjansen4857/pathplanner/releases/download/v2026.1.2/PathPlanner-Linux-v2026.1.2.zip" -O /tmp/pathplanner.zip +unzip -q /tmp/pathplanner.zip -d ~/wpilib/2026/pathplanner +chmod +x ~/wpilib/2026/pathplanner/pathplanner +rm /tmp/pathplanner.zip +``` + +Unlike `AdvantageScope`/`Elastic`, **do not** add this to `$TOOLS_DIR`/`tools.json` — those exist specifically for the vscode-wpilib extension's built-in "WPILib: Start Tool" quick-pick, which has a fixed list of known WPILib tool names; PathPlanner isn't one of them, and dropping a same-named binary in that directory won't add it to that menu. Instead, expose it directly on `PATH` so students can run `pathplanner` from the integrated terminal or a `.vscode/tasks.json` task: + +**`shared-devcontainer/.devcontainer/devcontainer.json`** — extend the existing `remoteEnv`: + +```json +"remoteEnv": { + "DISPLAY": ":1", + "WPILIB_SIMULATION": "true", + "PATH": "${containerEnv:PATH}:/home/vscode/wpilib/2026/pathplanner" +} +``` + +**`shared-devcontainer/.devcontainer/Dockerfile`** or the `apt-get install` list in `post-create.sh` — PathPlanner's Linux bundle links GTK3, which isn't currently in the apt list (`libgl1`, `libxrender1`, `libxrandr2`, `libxinerama1`, `libxi6`, `libxext6`, `libx11-dev`, `xauth`, `x11-apps` — no GTK). Add `libgtk-3-0` to the `sudo apt-get install` list in `post-create.sh`. **Verify the exact package name at implementation time** — Ubuntu 24.04 (the Dockerfile's base image) renamed many `t64`-transitioned packages, so it may need to be `libgtk-3-0t64` instead. Confirm with `apt-cache search libgtk-3` inside the container before committing. + +Students launch it with `pathplanner` in the terminal (auto-detects the project's `src/main/deploy/pathplanner/` folder when run from the repo root), viewable through the same desktop-lite noVNC session (port 6080) already forwarded for WPILib sim GUIs — no new forwarded port needed. + +*** + +## Step 3 — New lesson directory: `PathPlanner-Autonomous/` + +Mirror `Swerve-Drive/`'s layout exactly (confirmed file-for-file from the real repo): + +``` +PathPlanner-Autonomous/ +├── .githooks/pre-push # copy from Swerve-Drive/.githooks +├── .gitignore # copy from Swerve-Drive/.gitignore +├── .vscode/{launch.json,settings.json} # copy from Swerve-Drive/.vscode +├── .wpilib/wpilib_preferences.json # copy from Swerve-Drive/.wpilib (projectYear templated by setup-lesson.yml) +├── WPILib-License.md # copy from Swerve-Drive +├── build.gradle, settings.gradle # copy from Swerve-Drive +├── gradle/wrapper/*, gradlew, gradlew.bat # copy from Swerve-Drive +├── README.md # one-liner title, matches Basic-DriveTrain's minimal style +├── vendordeps/ +│ ├── Phoenix6-26.3.0.json # copied from Swerve-Drive (mixed-hardware YAGSL support) +│ ├── REVLib.json, ReduxLib.json, ThriftyLib.json, WPILibNewCommands.json +│ ├── yagsl-2026.4.1.json +│ └── PathplannerLib-2026.1.2.json # NEW — https://3015rangerrobotics.github.io/pathplannerlib/PathplannerLib.json +├── src/main/deploy/ +│ ├── swerve/... # copied from Swerve-Drive's SOLUTION state (already filled in — this +│ │ # lesson is not re-teaching swerve config) +│ └── pathplanner/ +│ ├── settings.json # pre-seeded robot config for "RoboLancers Practice Swerve Bot" +│ │ # (reuse the module CAN IDs/offsets from Swerve-Drive's unit1 spec sheet; +│ │ # add fictional mass/MOI/motor-type numbers this lesson introduces) +│ ├── navgrid.json # default navgrid, from PathPlanner's own default_navgrid.json +│ ├── paths/ # empty — student creates PracticePath.path here in Unit 1 +│ └── autos/ # empty — student creates PracticeAuto.auto here in Unit 2 +├── src/main/java/frc/robot/ +│ ├── Robot.java, Main.java # copy from Swerve-Drive +│ ├── Constants.java # copy from Swerve-Drive +│ ├── RobotContainer.java # forked from Swerve-Drive's SOLUTION RobotContainer, but with +│ │ # AutoBuilder.configure(...) already wired (pre-completed — that +│ │ # plumbing was covered in the Swerve-Drive lesson's spirit and in +│ │ # docs/programming/pathplanner_autonomous.md, not a goal of *this* +│ │ # lesson), plus TWO new stub points for Units 3 & 4 (see below) +│ ├── subsystems/swervedrive/SwerveSubsystem.java # copy from Swerve-Drive's SOLUTION (already implemented) +│ └── subsystems/ExampleSubsystem.java # NEW, minimal — one boolean field (`running`) + a `setRunning(boolean)` +│ # command factory, so Unit 3's NamedCommand has something to observably +│ # affect without needing real hardware +├── src/test/java/frc/robot/ +│ ├── Unit3NamedCommandTest.java # NEW +│ ├── Unit4AutoChooserTest.java # NEW +│ └── Unit5AutonomousSimTest.java # NEW +├── grading/ +│ ├── check_path_waypoints.py # NEW (Unit 1) +│ └── check_auto_structure.py # NEW (Unit 2) +├── unit1-creating-a-path.md # NEW +├── unit2-creating-an-auto.md # NEW +├── unit3-named-command.md # NEW +├── unit4-auto-chooser.md # NEW +└── unit5-full-auto-sim-verification.md # NEW +``` + +### Starter-code stub points (what the student actually fills in) + +- **`RobotContainer.java`**: an unfinished `configureNamedCommands()` method (Unit 3 fills in the `NamedCommands.registerCommand(...)` call) and an unfinished `configureAutoChooser()` + `getAutonomousCommand()` (Unit 4 fills in `AutoBuilder.buildAutoChooser()` / `SmartDashboard.putData(...)` / `return autoChooser.getSelected();`) — same "stub method throws `UnsupportedOperationException` until implemented" pattern already used in `Unit3DriveCommandTest`'s target class. +- **PathPlanner GUI-authored files** (Units 1–2) aren't Java — they're `.path`/`.auto` JSON the student creates *in the app itself*, per the existing precedent of GUI/config-tool-authored JSON not going through hand-editing. + +*** + +## Step 4 — The five units + +Each unit follows the established format exactly (`Objective` / `Estimated time` / `What You'll Do` / `Why This Matters` / `Verification` / `Next Step`), and cross-links back to `docs/programming/pathplanner_overview.md` and `docs/programming/pathplanner_autonomous.md` from [pathplanner-tutorial-plan.md](pathplanner-tutorial-plan.md) the same way `Swerve-Drive`'s units link to `yagsl_swerve_tutorial.md`. + +### Unit 1 — Creating a Path +- **Objective**: launch `pathplanner` from the terminal, open the repo root as the project, fill in the robot config (mass/MOI/module geometry — reuses the "RoboLancers Practice Swerve Bot" identity from Swerve-Drive's spec sheet), then draw a path named `PracticePath` matching a given waypoint spec sheet (start pose, one interior waypoint, end pose, a max-velocity constraint zone). +- **Verification**: `python3 grading/check_path_waypoints.py` — parses `src/main/deploy/pathplanner/paths/PracticePath.path` JSON and checks waypoint anchor positions / constraint values against the spec, tolerant float comparison, `PASS`/`FAIL` per field (same shape as `check_swerve_config.sh`, ported to Python since `.path` JSON nesting is deeper). + +### Unit 2 — Creating an Auto +- **Objective**: in the GUI, create `PracticeAuto` that runs `PracticePath`, with a named-command event marker `"IntakeDown"` placed partway through (typed as plain text in the GUI — it doesn't need to resolve to a real Java command yet). +- **Verification**: `python3 grading/check_auto_structure.py` — parses `autos/PracticeAuto.auto`, checks it references `PracticePath` and contains an event marker whose name is exactly `IntakeDown` (event-marker string must match the `NamedCommands` key exactly — call this out explicitly, it's the single most common real-world PathPlanner mistake per both reference repos surveyed in the docs-page plan). + +### Unit 3 — Creating a NamedCommand +- **Objective**: in `RobotContainer.configureNamedCommands()`, call `NamedCommands.registerCommand("IntakeDown", exampleSubsystem.setRunning(true).withTimeout(1))` (or equivalent) so the Unit 2 event marker resolves to something real. +- **Verification**: `Unit3NamedCommandTest.java` — HAL-sim JUnit test (same style as `Unit3DriveCommandTest`) asserting `NamedCommands.hasCommand("IntakeDown")` and that scheduling the registered command drives `ExampleSubsystem.isRunning()` to `true`. + +### Unit 4 — Adding an AutoChooser +- **Objective**: in `RobotContainer.configureAutoChooser()` / `getAutonomousCommand()`, wire `autoChooser = AutoBuilder.buildAutoChooser(); SmartDashboard.putData("Auto Chooser", autoChooser);` and return `autoChooser.getSelected()`. +- **Verification**: `Unit4AutoChooserTest.java` — reflection check that `getAutonomousCommand()` is public/non-throwing, plus a HAL-sim check that it returns a non-null `Command` once `AutoBuilder` has paths loaded from the deploy directory. + +### Unit 5 — Full autonomous sequence: choose, run, verify in sim +- **Objective**: put it all together — confirm `PracticeAuto` is selectable and selected in the chooser, then actually run it. +- **What You'll Do**: `./gradlew simulateJava` (or VS Code's "WPILib: Simulate Robot Code"), open the sim GUI (Glass or AdvantageScope, both already installed) through the desktop-lite noVNC session, enable autonomous, and visually confirm the robot follows `PracticePath` on the field view and that the `IntakeDown` marker visibly fires (e.g. print a line to the console, or flip a SmartDashboard boolean, from `ExampleSubsystem` — there's no real hardware to observe). +- **Verification** (automated): `Unit5AutonomousSimTest.java` — an integration JUnit test that initializes HAL, schedules `robotContainer.getAutonomousCommand()` on `CommandScheduler`, advances simulated time in a loop (`SimHooks.stepTiming(...)` + `CommandScheduler.getInstance().run()`), and asserts (a) the command finishes within an expected time bound, (b) the drivetrain's final simulated pose is within tolerance of `PracticePath`'s end pose, and (c) `ExampleSubsystem.isRunning()` was observed `true` at some point during the run. This is the automated proxy for "verify it in sim" since the visual GUI confirmation itself can't be scripted — the unit's markdown explicitly frames the GUI step as the *real* verification and the JUnit test as the graded proxy. +- **Next Step**: none — this is the capstone; point back to `docs/programming/pathplanner_autonomous.md`'s debugging/telemetry section for what to do if the robot doesn't follow the path correctly (alliance-flip flag, event-marker name mismatches, `PPHolonomicDriveController` gains). + +*** + +## Step 5 — Wire into `setup-lesson.yml` (builds the *template repo*, not the student's copy) + +Two changes to `.github/workflows/setup-lesson.yml`: + +1. Add `"PathPlanner-Autonomous"` to the `lesson_name` `workflow_dispatch` choice `options` list. +2. Add a `setup-pathplanner-autonomous:` job, copy-pasted from `setup-swerve-drive:` with `if: inputs.lesson_name == 'PathPlanner-Autonomous'` and all `${{inputs.lesson_name}}` path references pointing at the new directory — it already copies `.devcontainer` from `shared-devcontainer` (Step 2 lands there automatically), `.githooks`, `.vscode`, `.wpilib`, gradle/build files, `grading/`, `src/`, and `vendordeps/`, which is exactly the same shape as this lesson. + +**Important correction from the original version of this plan:** this step only produces `RoboLancers-Java-Learning/PathPlanner-Autonomous-2026` — the shared **template** repo (`is_template: true`), same as `Swerve-Drive-2026` already is. It does **not** put the lesson in front of any student, and re-running it for each student is the wrong model. The earlier "Setup CI Workflow" step / branch-protection concern in the old Step 5 was also a conflation — `docs/CONFIGURATION.md`'s `spotless`/`build` branch-protection story governs the **team-season robot code repos** provisioned by `setup-repository.yml`, a different system from how classroom50 grades assignment repos (see Step 6). No CI workflow needs to be authored here at all. + +*** + +## Step 6 — Register it as a real classroom50 assignment (this is how students actually get it) + +Verified against the live config repo `RoboLancers-Java-Learning/classroom50`: it has one classroom, `frc-java`, with two assignments already registered in `frc-java/assignments.json` — `basic-driving-robot-talon` (template `Basic-DriveTrain-2026`) and `github-basics` (template `Github-Basics-2026`). **`Swerve-Drive-2026` is not yet registered as an assignment**, even though the template repo exists — that gap should probably be closed alongside this work, since `PathPlanner-Autonomous` is meant to follow it in sequence. + +**Register the assignment** (run once, by whoever holds the teacher role for `frc-java`): + +```sh +gh teacher assignment add RoboLancers-Java-Learning frc-java pathplanner-autonomous \ + --name "PathPlanner Autonomous" \ + --template RoboLancers-Java-Learning/PathPlanner-Autonomous-2026 \ + --description "Build and run an autonomous routine with PathPlanner, on top of a completed swerve drivetrain." \ + --due +``` + +This writes a new entry into `frc-java/assignments.json`, exactly like the two existing entries. If `Swerve-Drive-2026` also gets registered (see above), do it the same way with slug e.g. `swerve-drive`. + +**Author the five units' grading as declarative tests** — this is how `basic-driving-robot-talon` is already graded (its `tests` array is inline in `assignments.json`, each entry running `./gradlew test --tests '...'`), not via a bespoke CI/branch-protection workflow. Author one test per unit: + +```sh +gh teacher assignment test add RoboLancers-Java-Learning frc-java pathplanner-autonomous \ + --name "Unit 1: Creating a Path" --type run \ + --run "python3 grading/check_path_waypoints.py" --points 15 + +gh teacher assignment test add RoboLancers-Java-Learning frc-java pathplanner-autonomous \ + --name "Unit 2: Creating an Auto" --type run \ + --run "python3 grading/check_auto_structure.py" --points 15 + +gh teacher assignment test add RoboLancers-Java-Learning frc-java pathplanner-autonomous \ + --name "Unit 3: Creating a NamedCommand" --type run \ + --setup "chmod +x ./gradlew" --run "./gradlew test --tests 'frc.robot.Unit3NamedCommandTest'" --points 20 --timeout 300 + +gh teacher assignment test add RoboLancers-Java-Learning frc-java pathplanner-autonomous \ + --name "Unit 4: Adding an AutoChooser" --type run \ + --run "./gradlew test --tests 'frc.robot.Unit4AutoChooserTest'" --points 20 --timeout 300 + +gh teacher assignment test add RoboLancers-Java-Learning frc-java pathplanner-autonomous \ + --name "Unit 5: Full autonomous sequence in sim" --type run \ + --run "./gradlew test --tests 'frc.robot.Unit5AutonomousSimTest'" --points 30 --timeout 300 +``` + +(Or author all five at once as a bare JSON array and pass `--tests units.json` to `assignment add`.) On the next push to the config repo, `publish-pages.yaml` runs `materialize_tests.py`, which writes these into `frc-java/autograders/pathplanner-autonomous/tests.json` and bundles it for `runner.py` to fetch at grade time — no hand-written `autograder.py` needed, matching the pattern already used for `basic-driving-robot-talon`. + +**Why this resolves "build off a completed lesson":** classroom50's `gh student accept` always generates a student's repo from the *same static template* — there is no mechanism to fork forward from each individual student's own prior submission, and there shouldn't be (a buggy Swerve-Drive solution would otherwise propagate into every later assignment's grading). The correct model, and the one this plan already uses in Step 3, is: the **template's starter files** are the canonical, already-solved Swerve-Drive state (the answer key), authored once by a mentor into `management/PathPlanner-Autonomous/`, not sourced from any individual student's repo. Every student who accepts this assignment starts from that same known-good baseline, same as how `Swerve-Drive`'s own starter doesn't literally fork anyone's `Basic-DriveTrain` submission either. + +**How a student actually gets the lesson**, once registered: + +```sh +gh student accept RoboLancers-Java-Learning frc-java pathplanner-autonomous +``` + +This creates `RoboLancers-Java-Learning/frc-java-pathplanner-autonomous-`, generated from the template, with the autograde shim already wired in. `gh student submit` (or a plain `git push origin main`) triggers grading against the five declarative tests above, and `collect-scores.yaml` rolls the result into `frc-java/scores.json` like every other assignment. + +*** + +## Verification (end-to-end, before opening the PR) + +1. Locally build the devcontainer image (or push the branch and let Codespaces prebuild) and confirm `pathplanner` launches from the integrated terminal and shows the field view over noVNC. +2. From a scratch checkout of `PathPlanner-Autonomous/`, complete Units 1–2 by hand in the GUI and confirm `grading/check_path_waypoints.py` and `grading/check_auto_structure.py` both print all-`PASS`. +3. Implement Units 3–5's fill-ins and run `./gradlew test` — all `UnitNXxxTest` classes should pass. +4. Run `./gradlew simulateJava`, confirm the auto actually drives the simulated pose along the path in the sim GUI. +5. Trigger `workflow_dispatch` on `setup-lesson.yml` with `lesson_name: PathPlanner-Autonomous` to produce `PathPlanner-Autonomous-2026`, and confirm it's flagged as a template repo. +6. Run Step 6's `gh teacher assignment add` / `assignment test add` commands against `frc-java`, then `gh teacher assignment list RoboLancers-Java-Learning frc-java --json` to confirm the new entry and its tests landed correctly. +7. As a test student (or with `--dry-run` if available), run `gh student accept RoboLancers-Java-Learning frc-java pathplanner-autonomous` and confirm the generated repo builds, and that a throwaway push triggers the autograde workflow and produces a `result.json` release with all five tests reporting. +8. Open the PR from `lesson/pathplanner-autonomous` into `management`'s `main` for mentor review (required per branch protection) — this only covers Steps 1–5; Step 6 happens separately against the `classroom50` config repo once the template PR is merged. diff --git a/improvement_analysis/pathplanner-tutorial-plan.md b/improvement_analysis/pathplanner-tutorial-plan.md new file mode 100644 index 0000000..7928efd --- /dev/null +++ b/improvement_analysis/pathplanner-tutorial-plan.md @@ -0,0 +1,83 @@ +# Plan: PathPlanner Overview + Creating Autonomous Routines with PathPlanner + +## Context + +`docs/programming/autonomous.md` currently teaches only hand-coded autonomous (`SequentialCommandGroup`, `WaitCommand`, drive-by-encoder-distance) and explicitly flags PathPlanner as future work in its own text ("replacing this line with a `SendableChooser` is a natural next step") and in `improvement_analysis/wip-completion-plan-autonomous.md` ("Add PathPlanner integration (optional but modern)"). The site also already has two complete swerve tutorials — [yagsl_swerve_tutorial.md](../docs/programming/yagsl_swerve_tutorial.md) and [ctre_swerve_generator_tutorial.md](../docs/programming/ctre_swerve_generator_tutorial.md) — whose code examples (`docs/code_examples/swerve/*`, `docs/code_examples/ctre_swerve/*`) already stub out an unused `SendableChooser autoChooser` and a `getAutonomousCommand()` returning `Commands.none()`. PathPlanner is the natural next tutorial to fill in those stubs. + +Two real, current (2026) team codebases were pulled as ground truth so the tutorial reflects actual usage instead of generic docs paraphrasing: +- **RoboLancers/321-Rebuilt-2026** (`develop` branch) — CTRE swerve generator (`Drivetrain extends TunerSwerveDrivetrain`), hand-wires `AutoBuilder.configure(...)` in its constructor, registers `NamedCommands`, uses `PathPlannerLogging` callbacks into a `Field2d`. +- **RoboLancers/427-Rebuilt-2026** (`main` branch) — YAGSL (`SwerveSubsystem`), same `AutoBuilder.configure(...)` pattern via `SwerveSetpointGenerator`, registers `NamedCommands` + an `EventTrigger`, and exposes a `followPathCommand(String)` helper using `PathPlannerPath.fromPathFile` + `AutoBuilder.followPath`. + +Both repos pin `vendordeps/PathplannerLib-2026.1.2.json`, confirming the current vendordep version/URL (`https://3015rangerrobotics.github.io/pathplannerlib/PathplannerLib.json`, resolves to 2026.1.2) and the standard deploy layout `src/main/deploy/pathplanner/{paths/, autos/, navgrid.json, settings.json}`. + +## Pages to Add + +### 1. `docs/programming/pathplanner_overview.md` — "PathPlanner Overview" + +Covers the GUI application and project setup, not robot code. Structure (per `template_page.md` / `documentation-writer-agent.md` conventions — contributor comment, `## Overview`, `***` dividers, `!!! tip/note` admonitions): + +- **What PathPlanner is** and why to use it over hand-coded autonomous (visual path editor, holonomic + differential drive support, on-the-fly replanning, GUI-driven event markers) — link back to `autonomous.md` as "what you'd otherwise hand-code." +- **Prerequisites**: a working swerve subsystem (link both swerve tutorials), PathPlanner GUI app installed, robot must be able to report/accept `RobotConfig` (mass, MOI, module offsets — same numbers already gathered for YAGSL/CTRE setup). +- **Installing PathplannerLib vendordep**: `https://3015rangerrobotics.github.io/pathplannerlib/PathplannerLib.json` via VSCode "Install New Libraries (online)", cross-reference `setup/3rd_party_libs.md`. Verify current season's exact URL/version via `frc-docs` MCP at write time (policy requirement) rather than hardcoding beyond what's confirmed here. +- **Deploy directory structure**: `src/main/deploy/pathplanner/paths/*.path`, `autos/*.auto`, `navgrid.json`, `settings.json` — shown as plain fenced code/dir-tree blocks (not `--8<--`, since these are GUI-generated artifacts, matching how the YAGSL tutorial inlines its GUI/config-tool-generated JSON rather than snippet-embedding it). +- **GUI walkthrough**: creating a project, entering robot config (mass/MOI/module locations/motor type — this is what `RobotConfig.fromGUISettings()` reads at runtime), drawing a path (waypoints, constraints, rotation targets, the navgrid for obstacle avoidance), building an Auto (chaining paths + named-command markers + wait commands), adding event markers. Use `![Image Title](imageURL)` placeholders per template since screenshots weren't captured in this pass — flag for a follow-up contributor/mentor to fill in. +- **Knowledge check** quiz block (matches `yagsl_swerve_tutorial.md` / `ctre_swerve_generator_tutorial.md` closing pattern). +- **Links to Relevant Documentation**: pathplanner.dev, GitHub repo, forward link to the second new page. + +No new Java code examples needed for this page — it's GUI/config focused. + +### 2. `docs/programming/pathplanner_autonomous.md` — "Creating Autonomous Routines with PathPlanner" + +Assumes the reader finished page 1 (paths/autos already exist in the deploy folder) and one of the two swerve tutorials. This is the code-heavy page, tabbed by vendor (`===` tabs, matching site convention) since the two existing swerve code bases diverge here: + +- **Wiring `AutoBuilder.configure`**: + - **YAGSL tab**: extend `docs/code_examples/swerve/SwerveSubsystem.java`'s constructor with a new `--8<-- [start:autobuilder-config]` anchor block modeled on 427-Rebuilt-2026's `SwerveSubsystem` — `RobotConfig.fromGUISettings()` in a try/catch, `SwerveSetpointGenerator`, `AutoBuilder.configure(this::getPose, this::resetPose, this::getSpeeds, (speeds, ff) -> driveFieldOriented(speeds), new PPHolonomicDriveController(translationPID, rotationPID), config, allianceBooleanSupplier, this)`. + - **CTRE Swerve Generator tab**: note that CTRE's generator emits this wiring itself in newer templates (verify exact current behavior via `frc-docs` MCP before writing — don't assert unconfirmed API shape); show the illustrative excerpt as a new file `docs/code_examples/ctre_swerve/CommandSwerveDrivetrain.java` (excerpt-only, "generator output, don't hand-type this" — same treatment as the existing `TunerConstants.java` illustrative file), based on 321-Rebuilt-2026's `Drivetrain.java` constructor pattern (`RobotConfig.fromGUISettings()` → `AutoBuilder.configure(getPose, resetPose, getChassisSpeeds, driveRobotRelative, ppHolonomicDriveController, config, MyAlliance::isRed, this)`). +- **Registering Named Commands**: `NamedCommands.registerCommand("Name", command)` in `RobotContainer`'s constructor, using the site's existing example subsystems (`ShooterSubsystem`/`ElevatorSubsystem` from `docs/examples/`) for realistic-but-generic command names rather than inventing new domain concepts — mirrors both real repos' pattern of registering intake/shoot commands before `configureBindings()`. +- **Filling in the `SendableChooser` stub**: complete `docs/code_examples/swerve/RobotContainer.java`'s existing unused `autoChooser` field and `docs/code_examples/ctre_swerve/RobotContainer.java`'s `getAutonomousCommand()` with `AutoBuilder.buildAutoChooser()` + `SmartDashboard.putData("Auto Chooser", autoChooser)` + `return autoChooser.getSelected();` — explicitly closes the loop `autonomous.md` opened ("a natural next step"). +- **Optional/advanced section**: `EventTrigger` for in-path-triggered behavior (427's pattern) and on-the-fly single-path following via `PathPlannerPath.fromPathFile(name)` + `AutoBuilder.followPath(path)` (427's `followPathCommand` helper) for cases simpler than a full GUI-built Auto. +- **Debugging/telemetry**: `PathPlannerLogging.setLogCurrentPoseCallback` / `setLogTargetPoseCallback` / `setLogActivePathCallback` wired to a `Field2d`, viewable in Elastic/Shuffleboard — cross-link `programming/Elastic.md`. Common gotchas: alliance-flip flag (`MyAlliance::isRed` / `DriverStation.getAlliance()`), event-marker string must exactly match the `NamedCommands` key, bumper-on-blocks test before field use. +- **Knowledge check** quiz + **Links to Relevant Documentation** (pathplanner.dev, back-links to `autonomous.md` and both swerve tutorials). + +New/modified code example files: +- Edit `docs/code_examples/swerve/SwerveSubsystem.java` — add `autobuilder-config` anchor. +- Edit `docs/code_examples/swerve/RobotContainer.java` — fill in `autoChooser` wiring + `NamedCommands` anchor. +- Edit `docs/code_examples/ctre_swerve/RobotContainer.java` — fill in chooser wiring + `NamedCommands` anchor. +- New `docs/code_examples/ctre_swerve/CommandSwerveDrivetrain.java` — illustrative generator-output excerpt showing `configureAutoBuilder()` (or whatever the generator currently calls it — confirm via `frc-docs` MCP). + +## Navigation + +Add both pages to `mkdocs.yml`'s `FRC Programming` nav block, after `autonomous.md` and before `pid.md`: + +```yaml + - 'programming/autonomous.md' + - 'programming/pathplanner_overview.md' + - 'programming/pathplanner_autonomous.md' + - 'programming/pid.md' +``` + +## Cross-linking existing pages (small edits, not new pages) + +- `docs/programming/autonomous.md`: turn the existing `!!! note "Why not use the chooser?"` aside into a forward-link to `pathplanner_autonomous.md`. +- `docs/programming/yagsl_swerve_tutorial.md` and `docs/programming/ctre_swerve_generator_tutorial.md`: their existing "PathPlanner" mentions (in the ChassisSpeeds note and Phoenix6-Examples link respectively) become real links to `pathplanner_overview.md`. + +## Execution notes (per AGENT.md policy) + +- Query the `frc-docs` MCP server before writing any WPILib/vendor API content, especially: (a) whether CTRE's current swerve generator auto-generates `AutoBuilder` wiring in `CommandSwerveDrivetrain`, and (b) current-season `PathplannerLib` vendordep URL/version, to confirm the 2026.1.2 figures gathered here are still current at write time. +- All Java snippets go through the `--8<--` anchor pattern except GUI-generated JSON/`.path`/`.auto` artifacts, which are shown inline (matches existing YAGSL JSON-config precedent). +- Use the Documentation Writer agent (`.claude/documentation-writer-agent.md` via `general` subagent) to draft both pages in the established voice/format, feeding it this plan plus the concrete code excerpts already gathered from both reference repos. + +## Verification + +- `mkdocs build` (or `mkdocs serve`) from repo root with no warnings about missing snippet anchors or broken nav entries. +- Manually check both new pages render in the local `mkdocs serve` preview: tabs switch correctly, quiz blocks render, all `--8<--` includes resolve. +- Confirm every new anchor tag added to existing `.java` files is actually referenced by a page (and vice versa) so nothing is dead. + +## Reference material pulled from real repos (for implementation) + +Concrete code already retrieved during planning that the eventual implementer/agent should reuse rather than re-fetch: + +- **427-Rebuilt-2026** `RobotContainer.java`: `NamedCommands.registerCommand(...)`, `EventTrigger`, `PathPlannerLogging` → `Field2d` callbacks, `AutoBuilder.buildAutoChooser()`. +- **427-Rebuilt-2026** `SwerveSubsystem.java`: `RobotConfig.fromGUISettings()`, `SwerveSetpointGenerator`, `AutoBuilder.configure(...)` with `PPHolonomicDriveController`, `followPathCommand(String)` via `PathPlannerPath.fromPathFile` + `AutoBuilder.followPath`. +- **321-Rebuilt-2026** `Drivetrain.java` (extends `TunerSwerveDrivetrain`): `RobotConfig.fromGUISettings()` → `AutoBuilder.configure(getPose, resetPose, getChassisSpeeds, driveRobotRelative, ppHolonomicDriveController, config, MyAlliance::isRed, this)`. +- Both repos' `vendordeps/PathplannerLib-2026.1.2.json` and `src/main/deploy/pathplanner/` layout (`autos/`, `paths/`, `navgrid.json`, `settings.json`). diff --git a/mkdocs.yml b/mkdocs.yml index a4e5f17..076a240 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -29,6 +29,7 @@ nav: - 'programming/driving_robot.md' - 'programming/SwerveDriveIntro.md' - 'programming/yagsl_swerve_tutorial.md' + - 'programming/ctre_swerve_generator_tutorial.md' - 'programming/deploying.md' - 'programming/using_sensors.md' # - 'programming/pneumatics.md' @@ -37,7 +38,6 @@ nav: - 'programming/robotpreferences.md' - 'programming/autonomous.md' - 'programming/pid.md' - #- 'programming/AdvantageKit.md' - 'programming/AKitStructureReference.md' # - 'programming/super_core.md' - Example Subsystems: