Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Build & Test

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

jobs:
build:
name: ./gradlew build
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up JDK 17 (matches WPILib 2026)
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'

- name: Cache Gradle wrapper + caches
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle', 'gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: |
gradle-${{ runner.os }}-

- name: Make gradlew executable
run: chmod +x ./gradlew

- name: Build + run all tests
run: ./gradlew build --no-daemon --stacktrace

- name: Upload test report on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: gradle-test-report
path: |
build/reports/tests/test/
build/test-results/test/
retention-days: 7
10 changes: 10 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,21 @@ dependencies {

testImplementation 'org.junit.jupiter:junit-jupiter:5.10.1'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testImplementation 'org.assertj:assertj-core:3.26.3'
testImplementation 'org.mockito:mockito-core:5.14.2'
testImplementation 'org.mockito:mockito-junit-jupiter:5.14.2'
}

test {
useJUnitPlatform()
systemProperty 'junit.jupiter.extensions.autodetection.enabled', 'true'
maxParallelForks = Math.max(1, Runtime.runtime.availableProcessors().intdiv(2))
forkEvery = 50
testLogging {
events 'failed', 'skipped'
showStandardStreams = false
exceptionFormat = 'full'
}
}

// Simulation configuration (e.g. environment variables).
Expand Down
Empty file modified gradlew
100644 → 100755
Empty file.
2 changes: 0 additions & 2 deletions src/main/java/frc/robot/Binds.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import com.ctre.phoenix6.swerve.SwerveRequest;
import edu.wpi.first.wpilibj.DriverStation;
import frc.robot.commands.AutoAlign;
//import frc.robot.commands.DefenseBoost;
import frc.robot.commands.GyroReset;
import frc.robot.commands.ShootCommand;
import frc.robot.subsystem.Hopper;
Expand Down Expand Up @@ -69,7 +68,6 @@ public static final void bind(){
HumanControls.OperatorPanel.L1.whileTrue(Intake.get().doOscilateIntake());
HumanControls.OperatorPanel.L2.whileTrue(Swerve.get().xMode());
HumanControls.OperatorPanel.L3.whileTrue(new ShootCommand(()->Field.Alliance_Find.hub));
//HumanControls.OperatorPanel.L4.whileTrue(new DefenseBoost(120, 50));
HumanControls.OperatorPanel.Processor.whileTrue(Shooter.get().spinUpCommand());


Expand Down
61 changes: 61 additions & 0 deletions src/main/java/frc/robot/NamedCommandRegistry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package frc.robot;

import edu.wpi.first.wpilibj2.command.Command;
import frc.robot.commands.ShootCommand;
import frc.robot.subsystem.Intake;
import frc.robot.subsystem.Shooter;
import frc.robot.util.Auto;
import frc.robot.util.Field;
import java.util.Set;
import java.util.function.Supplier;

/**
* Single source of truth for every PathPlanner "named command" the robot exposes.
*
* <p>{@link Robot#Robot()} should build its {@code Auto.NamedCommand[]} from {@link #all()} (so
* adding a new named command happens in exactly one place), and {@link NamedCommandConsistencyTest}
* cross-checks {@link #NAMES} against every {@code .auto} JSON file in the deploy directory so an
* auto referencing a non-existent named command fails at build time instead of silently no-op'ing
* during a match.
*/
public final class NamedCommandRegistry {
private NamedCommandRegistry() {}

/** Every name that {@link #all()} returns, exposed as a stable set for cross-checking. */
public static final Set<String> NAMES =
Set.of(
"Shoot",
"ShootForever",
"Shorter Shoot",
"Intake_",
"Intake Forever",
"Do stow",
"Rev up",
"Oscilate",
"OscilateForever",
"Outtake");

/**
* Build the live {@link Auto.NamedCommand} array. Lazily constructs each command on call (the
* caller is expected to invoke this exactly once at robot init).
*/
public static Auto.NamedCommand[] all() {
Supplier<edu.wpi.first.math.geometry.Translation2d> hub = () -> Field.Alliance_Find.hub;
return new Auto.NamedCommand[] {
named("Shoot", new ShootCommand(hub).withTimeout(5.5)),
named("ShootForever", new ShootCommand(hub)),
named("Shorter Shoot", new ShootCommand(hub).withTimeout(2.5)),
named("Intake_", Intake.get().doIntake().withTimeout(2)),
named("Intake Forever", Intake.get().doIntake()),
named("Do stow", Intake.get().doStow().withTimeout(0.5)),
named("Rev up", Shooter.get().spinUpCommand()),
named("Oscilate", Intake.get().doOscilateIntake().withTimeout(4.0)),
named("OscilateForever", Intake.get().doOscilateIntake()),
named("Outtake", Intake.get().doOuttake().withTimeout(3.5))
};
}

private static Auto.NamedCommand named(String name, Command command) {
return new Auto.NamedCommand(name, command);
}
}
36 changes: 8 additions & 28 deletions src/main/java/frc/robot/Robot.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@
import edu.wpi.first.wpilibj.TimedRobot;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.CommandScheduler;
import frc.robot.commands.ShootCommand;
import frc.robot.subsystem.Intake;
import frc.robot.subsystem.LED;
import frc.robot.subsystem.Shooter;
import frc.robot.subsystem.Swerve;
import frc.robot.util.Auto;
import frc.robot.util.BotConstants;
Expand All @@ -34,41 +32,23 @@ public class Robot extends TimedRobot {
public Robot() {
this.logger = new Telemetry(BotConstants.DriveConstants.MaxSpeed);

Auto.initialize(
new Auto.NamedCommand("Shoot", new ShootCommand(()->Field.Alliance_Find.hub).withTimeout(5.5)),

new Auto.NamedCommand("ShootForever", new ShootCommand(()->Field.Alliance_Find.hub)),


new Auto.NamedCommand("Shorter Shoot", new ShootCommand(()->Field.Alliance_Find.hub).withTimeout(2.5)),

new Auto.NamedCommand("Intake_", Intake.get().doIntake().withTimeout(2)),

new Auto.NamedCommand("Intake Forever", Intake.get().doIntake()),

new Auto.NamedCommand("Do stow", Intake.get().doStow().withTimeout(0.5)),

new Auto.NamedCommand("Rev up", Shooter.get().spinUpCommand()),


new Auto.NamedCommand("Oscilate", Intake.get().doOscilateIntake().withTimeout(4.0)),

new Auto.NamedCommand("OscilateForever", Intake.get().doOscilateIntake()),


new Auto.NamedCommand("Outtake", Intake.get().doOuttake().withTimeout(3.5))

);
// The names below are the single source of truth maintained in NamedCommandRegistry;
// NamedCommandConsistencyTest cross-checks them against every .auto JSON file so an
// auto referencing a non-existent named command fails at build time.
Auto.initialize(NamedCommandRegistry.all());

Binds.DriverStation2026.bind();
Binds.OperatorPanel.bind();
// Brownout threshold is intentionally lowered from the WPILib default of 6.75 V
// to give the motors more sag headroom under simultaneous swerve + shooter +
// intake load. Trade-off: less protection against deeper brown-outs. Revisit if
// we see Rio resets during matches.
RobotController.setBrownoutVoltage(6.0);


//Binds.Controller.bind();

Intake.get().zeroEncoder();
Intake.get().SlotZeroConfigIntake();
LED.get().tuffAnimation();
//SignalLogger.start();
//DataLogManager.start();
Expand Down
55 changes: 0 additions & 55 deletions src/main/java/frc/robot/commands/DefenseBoost.java

This file was deleted.

57 changes: 26 additions & 31 deletions src/main/java/frc/robot/commands/ShootCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,62 +13,57 @@
import frc.robot.subsystem.Shooter;
import frc.robot.subsystem.Swerve;
import frc.robot.util.BotConstants;
import frc.robot.util.ShooterControlPolicy;

/* You should consider using the more terse Command factories API instead https://docs.wpilib.org/en/stable/docs/software/commandbased/organizing-command-based.html#defining-commands */
public class ShootCommand extends Command {

Supplier<Translation2d> desiredPose;
boolean isAtspeed;
/** Creates a new ShootCommand. */
public ShootCommand(Supplier<Translation2d> desired_pose) {

desiredPose = desired_pose;
private final Supplier<Translation2d> desiredPose;
/**
* Hysteresis latch state. Recomputed per execute() loop by ShooterControlPolicy.
* Reset to false in initialize() so each command instance starts from cold.
*/
private boolean isFiring;

this.addRequirements(Shooter.get(),Hopper.get(), LED.get());
// Use addRequirements() here to declare subsystem dependencies.
public ShootCommand(Supplier<Translation2d> desired_pose) {
this.desiredPose = desired_pose;
addRequirements(Shooter.get(), Hopper.get(), LED.get());
}

// Called when the command is initially scheduled.
@Override
public void initialize() {
isAtspeed = false;
isFiring = false;
}

// Called every time the scheduler runs while the command is scheduled.
@Override
public void execute() {
double distMeters=Swerve.get().distTo(desiredPose.get());
double velocity = BotConstants.Shooter.ShooterTable.get(distMeters);
double intakespeed = BotConstants.Shooter.backSpinTable.get(distMeters);

Shooter.get().set_velocity(velocity);

double distMeters = Swerve.get().distTo(desiredPose.get());
ShooterControlPolicy.Decision decision = ShooterControlPolicy.compute(
distMeters,
Shooter.get().getRollerVelocity(),
BotConstants.Shooter.ShooterTable,
BotConstants.Shooter.backSpinTable,
ShooterControlPolicy.DEFAULT_ENTER_TOL,
ShooterControlPolicy.DEFAULT_EXIT_TOL,
isFiring);

Shooter.get().set_velocity(decision.flywheelTarget());
LED.get().LEDyellowBlink();

if((Math.abs(Shooter.get().getRollerVelocity()-velocity))<0.95){
isAtspeed = true;
}


if(isAtspeed){
Shooter.get().intake_shooter(intakespeed);
isFiring = decision.fireIndexer();
if (isFiring) {
Shooter.get().intake_shooter(decision.indexerSpeed());
Hopper.get().run_Hopper();
}

}

// Called once the command ends or is interrupted.
@Override
public void end(boolean interrupted) {
Hopper.get().Stop();
Hopper.get().stop();
Shooter.get().Stop();

}

// Returns true when the command should end.
@Override
public boolean isFinished() {
return false;
}
//d
}
15 changes: 14 additions & 1 deletion src/main/java/frc/robot/subsystem/Hopper.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,20 @@ public Command oscilateHopper(){
}


//Stops
/**
* Synchronously stops the hopper motor right now. Use this from inside other commands'
* {@code end()} / {@code execute()} methods where the caller would otherwise discard the
* {@link Command} returned by {@link #Stop()} and the motor wouldn't actually stop.
*/
public void stop() {
m_Hopper.stopMotor();
}

/**
* Returns a Command that holds the motor stopped. ONLY intended for use as the hopper's
* default command in {@code Binds}. Other callers should use {@link #stop()} (void) — see
* HopperStopContractTest.
*/
public Command Stop(){
return run(()->{m_Hopper.stopMotor();});
}
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/frc/robot/subsystem/Intake.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@ public void positionIntake(Pivot pivot) {
m_IntakePivot.setControl(PivotPositionControl.withPosition(pivot.position).withSlot(0)); //
}

/**
* @deprecated Re-applies just the Slot0 PID gains of the pivot config. This was called
* redundantly at robot init on top of the full {@code cfg_Pivot} apply that already
* happens in {@link #Intake()}, and risks clobbering motor-controller settings that
* were configured between the full apply and this partial re-apply. Don't call this
* unless you're hot-tuning gains and know exactly what you're doing.
*/
@Deprecated
public void SlotZeroConfigIntake(){
m_IntakePivot.getConfigurator().apply(BotConstants.Intake.cfg_Pivot.Slot0);
}
Expand Down
Loading