Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
08a615e
Add some targeting for the potential 'superstructure'
tricktrap Jan 9, 2026
8127ec1
Publish calculated aim direction
tricktrap Jan 10, 2026
9b2e380
Add aiming command
tricktrap Jan 10, 2026
dab94b4
One last stab before mechanism debugging
tricktrap Jan 11, 2026
4f62ec4
Add override while working from mac
tricktrap Jan 11, 2026
84bfce0
Finally
tricktrap Jan 11, 2026
05582e5
Factor out aiming into its own command
tricktrap Jan 11, 2026
51e21bd
Add basic framework for range tables for shooter
tricktrap Jan 11, 2026
ef0edcf
Start correcting 3d axis oopsies
tricktrap Jan 11, 2026
cee56d1
Finally work out most of the rest of the coordinate issues
tricktrap Jan 11, 2026
2688df9
Improve simulated shooting with half-updated simulation of fuel
tricktrap Jan 12, 2026
6705efa
Merge in RI3D branch as of successful shooter test
tricktrap Jan 13, 2026
8a1e110
Merge branch 'ri3d' of github.com:CranberryAlarm/CA26_RobotCode into …
tricktrap Jan 13, 2026
95151e1
Merge in RI3D panic code from Tuesday lol
tricktrap Jan 13, 2026
2b11aa8
Switch hood to passive-only implementation for now
tricktrap Jan 15, 2026
0f70cbe
Fix indentation and line length
tricktrap Jan 17, 2026
0074041
Shorten fake time of flight lookup table to make situation with aimpo…
tricktrap Jan 17, 2026
18719f7
Re-use negated correctiveVector correctly for 3d visualization
tricktrap Jan 17, 2026
e23f9f5
Switch back to hard-coded simulated launch velocity for now
tricktrap Jan 17, 2026
cbb2ce6
Re-use hood's angle since the subsystem can be used again
tricktrap Jan 17, 2026
0ecdd29
Speed control for shooting works
dracco1993 Jan 17, 2026
9800945
Slightly better tuning
dracco1993 Jan 17, 2026
0109e6b
Merge branch 'ri3d' of github.com:CranberryAlarm/CA26_RobotCode into …
tricktrap Jan 19, 2026
e23c8fc
Clean up ShooterSubsystem
tricktrap Jan 19, 2026
80f4533
Turret is workably tuned
dracco1993 Jan 19, 2026
574704c
Merge in latest turret control code from Ri3D
tricktrap Jan 19, 2026
e5a74c3
WIP
dracco1993 Jan 19, 2026
6d8d5e4
Not better, but different
dracco1993 Jan 19, 2026
465737a
It's working, but aimed wrong
dracco1993 Jan 20, 2026
eaff02a
Invert the turret motor
dracco1993 Jan 20, 2026
26881df
Translation is now working, rotation is a no
dracco1993 Jan 20, 2026
2d97a7f
WIP
dracco1993 Jan 20, 2026
51ac183
AS export
dracco1993 Jan 20, 2026
00f4cb5
Add 3D models for better debugging
dracco1993 Jan 20, 2026
f5c5dd8
It's working! (maybe) 🤞
dracco1993 Jan 20, 2026
d949da4
Set correct p-value
dracco1993 Jan 20, 2026
f514bb0
Support for aimed turret in 3d field
dracco1993 Jan 20, 2026
7fea7cf
Cleanup, started work on auto shooting
dracco1993 Jan 20, 2026
625b4e2
SOTM works, and is _mostly_ tuned 🤞
dracco1993 Jan 21, 2026
0629f9c
Hacked 2026 maplesim into things
dracco1993 Jan 21, 2026
90131d0
Fuel firing sim works
dracco1993 Jan 21, 2026
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
531 changes: 491 additions & 40 deletions AdvantageScopeSwerve.json

Large diffs are not rendered by default.

38 changes: 38 additions & 0 deletions assets/Robot_CA26/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "2026 CA26",
"rotations": [
{
"axis": "x",
"degrees": 90.0
},
{
"axis": "y",
"degrees": 0.0
},
{
"axis": "z",
"degrees": 90.0
}
],
"position": [0.0, 0.0, 0.0],
"cameras": [],
"components": [
{
"zeroedRotations": [
{
"axis": "x",
"degrees": 90.0
},
{
"axis": "y",
"degrees": 0.0
},
{
"axis": "z",
"degrees": 90.0
}
],
"zeroedPosition": [0.205, 0.0, -0.375]
}
]
}
Binary file added assets/Robot_CA26/model.glb
Binary file not shown.
Binary file added assets/Robot_CA26/model_0.glb
Binary file not shown.
4 changes: 0 additions & 4 deletions simgui-ds.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,6 @@
"guid": "78696e70757401000000000000000000",
"useGamepad": true
},
{
"guid": "78696e70757401000000000000000000",
"useGamepad": true
},
{
"guid": "78696e70757401000000000000000000",
"useGamepad": true
Expand Down
21 changes: 13 additions & 8 deletions src/main/java/frc/robot/Robot.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package frc.robot;

import org.ironmaple.simulation.SimulatedArena;
import org.ironmaple.simulation.seasonspecific.reefscape2025.ReefscapeAlgaeOnField;
import org.littletonrobotics.junction.LoggedRobot;
import org.littletonrobotics.junction.Logger;
import org.littletonrobotics.junction.networktables.NT4Publisher;

import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.math.geometry.Translation2d;
import edu.wpi.first.math.geometry.Rotation3d;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.CommandScheduler;
import frc.robot.util.CommandsLogging;
import frc.robot.util.maplesim.Arena2026Rebuilt;

public class Robot extends LoggedRobot {
private Command m_autonomousCommand;
Expand Down Expand Up @@ -52,16 +52,15 @@ public void robotPeriodic() {
CommandsLogging.logRequiredSubsystems();

if (Robot.isSimulation()) {
Pose3d[] algaePoses = arena.getGamePiecesArrayByType("Algae");
Logger.recordOutput("FieldSimulation/AlgaePoses", algaePoses);

Pose3d[] coralPoses = arena.getGamePiecesArrayByType("Coral");
Logger.recordOutput("FieldSimulation/CoralPoses", coralPoses);
Pose3d[] fuelPoses = arena.getGamePiecesArrayByType("Fuel");
Logger.recordOutput("FieldSimulation/FuelPoses", fuelPoses);
}

Logger.recordOutput("FieldSimulation/RobotPose", m_robotContainer.getRobotPose());
Logger.recordOutput("FieldSimulation/TargetPose",
m_robotContainer.getSwerveDrive().field.getObject("targetPose").getPose());
Logger.recordOutput("FieldSimulation/AimDirection", m_robotContainer.getAimDirection());
Logger.recordOutput("FieldSimulation/AimTarget", new Pose3d(m_robotContainer.getAimPoint(), Rotation3d.kZero));
}

@Override
Expand Down Expand Up @@ -108,9 +107,15 @@ public void testPeriodic() {

@Override
public void simulationInit() {
// Shut down the old arena instance first to release ownership of all bodies
// (including the drivetrain) so they can be added to a new physics world
SimulatedArena.getInstance().shutDown();

SimulatedArena.overrideInstance(new Arena2026Rebuilt());

arena = SimulatedArena.getInstance();

arena.addGamePiece(new ReefscapeAlgaeOnField(new Translation2d(14, 1)));
arena.addDriveTrainSimulation(m_robotContainer.getSwerveDrive().getMapleSimDrive().get());
}

@Override
Expand Down
29 changes: 24 additions & 5 deletions src/main/java/frc/robot/RobotContainer.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import com.pathplanner.lib.auto.NamedCommands;

import edu.wpi.first.math.geometry.Pose2d;
import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.math.geometry.Transform3d;
import edu.wpi.first.math.geometry.Translation3d;
import edu.wpi.first.wpilibj.DriverStation;
import edu.wpi.first.wpilibj.Filesystem;
import edu.wpi.first.wpilibj.smartdashboard.SendableChooser;
Expand All @@ -16,24 +19,26 @@
import frc.robot.Constants.ControllerConstants;
import frc.robot.controls.DriverControls;
import frc.robot.controls.OperatorControls;
import frc.robot.controls.PoseControls;
import frc.robot.subsystems.HoodSubsystem;
import frc.robot.subsystems.HopperSubsystem;
import frc.robot.subsystems.IntakeSubsystem;
import frc.robot.subsystems.KickerSubsystem;
import frc.robot.subsystems.ShooterSubsystem;
import frc.robot.subsystems.Superstructure;
import frc.robot.subsystems.SwerveSubsystem;
import frc.robot.subsystems.TurretSubsystem;
import swervelib.SwerveDrive;

public class RobotContainer {
private final SwerveSubsystem drivebase = new SwerveSubsystem(new File(Filesystem.getDeployDirectory(), "swerve"));

private final TurretSubsystem turret = new TurretSubsystem();
private final ShooterSubsystem shooter = new ShooterSubsystem();
private final IntakeSubsystem intake = new IntakeSubsystem();
private final HopperSubsystem hopper = new HopperSubsystem();
private final KickerSubsystem kicker = new KickerSubsystem();
private final ShooterSubsystem shooter = new ShooterSubsystem();
private final HoodSubsystem hood = new HoodSubsystem();

private final Superstructure superstructure = new Superstructure(shooter, null, null, intake, hopper, kicker);
private final Superstructure superstructure = new Superstructure(shooter, turret, hood, intake, hopper, kicker);

private final SendableChooser<Command> autoChooser;

Expand All @@ -45,7 +50,7 @@ public RobotContainer() {
configureBindings();
buildNamedAutoCommands();

if (!Robot.isReal()) {
if (!Robot.isReal() || true) {
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition || true makes this always evaluate to true, meaning joystick connection warnings will be silenced even on real robots. This appears to be debug code that should be removed.

Suggested change
if (!Robot.isReal() || true) {
if (!Robot.isReal()) {

Copilot uses AI. Check for mistakes.
DriverStation.silenceJoystickConnectionWarning(true);
}

Expand Down Expand Up @@ -102,4 +107,18 @@ public SwerveDrive getSwerveDrive() {
public Pose2d getRobotPose() {
return drivebase.getPose();
}

public Pose3d getAimDirection() {
// Apply robot heading first, then turret/hood rotation on top
Pose3d shooterPose = superstructure.getShooterPose();

var pose = drivebase.getPose3d().plus(new Transform3d(
shooterPose.getTranslation(), shooterPose.getRotation()));

return pose;
}

public Translation3d getAimPoint() {
return superstructure.getAimPoint();
}
}
169 changes: 169 additions & 0 deletions src/main/java/frc/robot/commands/ShootOnTheMoveCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package frc.robot.commands;

import java.util.Map;
import java.util.function.Supplier;

import org.littletonrobotics.junction.Logger;

import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.math.geometry.Rotation3d;
import edu.wpi.first.math.geometry.Translation2d;
import edu.wpi.first.math.geometry.Translation3d;
import edu.wpi.first.math.interpolation.InterpolatingDoubleTreeMap;
import static edu.wpi.first.units.Units.Degrees;
import static edu.wpi.first.units.Units.Meters;
import static edu.wpi.first.units.Units.RPM;
import edu.wpi.first.units.measure.Angle;
import edu.wpi.first.units.measure.AngularVelocity;
import edu.wpi.first.units.measure.Distance;
import edu.wpi.first.wpilibj2.command.Command;
import frc.robot.subsystems.Superstructure;
import frc.robot.subsystems.SwerveSubsystem;

public class ShootOnTheMoveCommand extends Command {
private final SwerveSubsystem drivetrain;
private final Superstructure superstructure;

private Supplier<Translation3d> aimPointSupplier; // The point to aim at
private AngularVelocity latestShootSpeed;
private Angle latestHoodAngle;
private Angle latestTurretAngle;

public ShootOnTheMoveCommand(SwerveSubsystem drivetrain, Superstructure superstructure,
Supplier<Translation3d> aimPointSupplier) {
this.drivetrain = drivetrain;
this.superstructure = superstructure;
this.aimPointSupplier = aimPointSupplier;

// We use the drivetrain to determine linear velocity, but don't require it for
// control. We
// will be using the superstructure to control the shooting mechanism so it's a
// requirement.
// addRequirements(superstructure);

// TODO: figure out if the above is actually required. Right now, when you start
// some other command, the auto aim can't start back up again
}

@Override
public void initialize() {
super.initialize();

latestHoodAngle = superstructure.getHoodAngle();
latestTurretAngle = superstructure.getTurretAngle();
latestShootSpeed = superstructure.getShooterSpeed();

// TODO: when this current command ends, we should probably cancel the dynamic
// aim command
superstructure.aimDynamicCommand(
() -> {
return this.latestShootSpeed;
},
() -> {
return this.latestTurretAngle;
},
() -> {
return this.latestHoodAngle;
}).schedule();
}

@Override
public boolean isFinished() {
return false;
}

@Override
public void execute() {
// Calculate trajectory to aimPoint
var target = aimPointSupplier.get();

var shooterLocation = drivetrain.getPose3d().getTranslation()
.plus(superstructure.getShooterPose().getTranslation());

// Ignore this parameter for now, the range tables will account for it :/
// var deltaH = target.getMeasureZ().minus(shooterLocation.getMeasureZ());
var shooterOnGround = new Translation2d(shooterLocation.getX(), shooterLocation.getY());
var targetOnGround = new Translation2d(target.getX(), target.getY());

var distanceToTarget = Meters.of(shooterOnGround.getDistance(targetOnGround));

// Get time of flight. We could try to do this analytically but for now it's
// easier and more realistic
// to use a simple linear approximation based on empirical data.
double timeOfFlight = getFlightTime(distanceToTarget);

// Calculate corrective vector based on our current velocity multiplied by time
// of flight.
// If we're stationary, this should be zero. If we're backing up, this will be
// "ahead" of the target, etc.
var updatedPosition = drivetrain.getFieldVelocity().times(timeOfFlight);
var correctiveVector = new Translation2d(updatedPosition.vxMetersPerSecond, updatedPosition.vyMetersPerSecond)
.unaryMinus();
var correctiveVector3d = new Translation3d(correctiveVector.getX(), correctiveVector.getY(), 0);

Logger.recordOutput("FieldSimulation/AimTargetCorrected",
new Pose3d(target.plus(correctiveVector3d), Rotation3d.kZero));

var correctedTarget = targetOnGround.plus(correctiveVector);

var vectorToTarget = drivetrain.getPose().getTranslation().minus(correctedTarget);

var correctedDistance = Meters.of(vectorToTarget.getNorm());
var calculatedHeading = vectorToTarget.getAngle()
.rotateBy(drivetrain.getHeading().unaryMinus())
.getMeasure();

Logger.recordOutput("ShootOnTheMove/RobotHeading", drivetrain.getHeading());
Logger.recordOutput("ShootOnTheMove/CalculatedHeading", calculatedHeading);
Logger.recordOutput("ShootOnTheMove/distanceToTarget", distanceToTarget);

latestTurretAngle = calculatedHeading;
latestShootSpeed = calculateRequiredShooterSpeed(correctedDistance);

// TODO: add this back if/when we have a real hood, for now, just set it to the
// current angle
// latestHoodAngle = calculateRequiredHoodAngle(correctedDistance);
latestHoodAngle = superstructure.getHoodAngle();

superstructure.setShooterSetpoints(
latestShootSpeed,
latestTurretAngle,
latestHoodAngle);

// System.out.println("Shooting at distance: " + correctedDistance + " requires
// speed: " + latestShootSpeed
// + ", hood angle: " + latestHoodAngle + ", turret angle: " +
// latestTurretAngle);
}

private double getFlightTime(Distance distanceToTarget) {
// Simple linear approximation based on empirical data.
return TIME_OF_FLIGHT_BY_DISTANCE.get(distanceToTarget.in(Meters));
}

private AngularVelocity calculateRequiredShooterSpeed(Distance distanceToTarget) {
return RPM.of(SHOOTING_SPEED_BY_DISTANCE.get(distanceToTarget.in(Meters)));
}

private Angle calculateRequiredHoodAngle(Distance distanceToTarget) {
return Degrees.of(HOOD_ANGLE_BY_DISTANCE.get(distanceToTarget.in(Meters)));
}

// meters, seconds
private static final InterpolatingDoubleTreeMap TIME_OF_FLIGHT_BY_DISTANCE = InterpolatingDoubleTreeMap.ofEntries(
Map.entry(1.0, 1.0),
Map.entry(4.86, 1.5));

// meters, RPS
private static final InterpolatingDoubleTreeMap SHOOTING_SPEED_BY_DISTANCE = InterpolatingDoubleTreeMap.ofEntries(
Map.entry(2.0, 2700.0),
Map.entry(3.0, 3000.0),
Map.entry(4.0, 3300.0),
Map.entry(4.86, 3750.0));

// meters, degrees
private static final InterpolatingDoubleTreeMap HOOD_ANGLE_BY_DISTANCE = InterpolatingDoubleTreeMap.ofEntries(
Map.entry(1.0, 15.0),
Map.entry(2.0, 30.0),
Map.entry(3.0, 45.0));
}
Loading