Shooter Subsystem Readme

Shooter subsystem

Overview

The Shooter subsystem spins up flywheels to launch FUEL across the field or into the scoring hub. It needs steady RPM so volleys stay accurate during REBUILT matches, with close-range and long-range setpoints to cover different scoring zones.

How it works

The shooter uses a dual-motor flywheel driven by two REV SparkMax motor controllers. Both motors are mechanically coupled to the same flywheel shaft. A feedforward term maintains the target velocity, and a PID controller corrects for disturbances when the indexer loads a piece and momentarily slows the wheel.

Dual-motor configuration

The competition robot uses two motors: a primary motor whose encoder provides position and velocity feedback, and a follower motor that mirrors the same voltage commands. Each motor has its own MotorConfig block in the JSON config, so they can have independent CAN IDs, inversion flags, and current limits.

At the code level, both motors are wrapped in a CompositeMotor adapter that implements the standard Motor interface. The subsystem hierarchy (AbstractMotorSubsystem, AbstractVelocitySubsystem) never knows multiple motors exist — it sees a single Motor.

On robots with only one shooter motor (e.g., the test robot), set followerMotorConfig.enabled = false in subsystems-test.json and the subsystem falls back to using the primary motor alone.

Units convention

The public API uses RPM (revolutions per minute) for all velocity values. Internally, the subsystem converts to radians per second for WPILib math (PID controllers, feedforward, trapezoidal profiles). RPM always refers to the mechanism (flywheel) speed, not the motor shaft speed. The gear ratio in MotorConfig.motorRotationsPerMechanismRotation handles the conversion between the two.

Key behaviors

  • Spin-up to target — commands set a target RPM and the shooter ramps to it using a PID controller plus feedforward. An optional trapezoidal motion profile limits acceleration for smoother spool-up.
  • Ready-to-fire signalisReadyToFire() returns true when the measured RPM is within velocityToleranceRpm of the target and has been stable for at least settleTimeSeconds. The indexer and autonomous commands wait on this signal before feeding.
  • Idle — when no shot is requested, the default IdleShooterCommand holds the flywheel at idleVelocityRpm so re-spool is fast.
  • ReversereverseVelocityRpm config field allows backward spin for clearing stuck pieces (command not yet wired).

Inheritance

The shooter extends the shared velocity abstraction layer:

AbstractSubsystem
  └─ AbstractMotorSubsystem        (motor, feedforward, SysId)
       └─ AbstractVelocitySubsystem  (PID velocity control, settle detection)
            └─ ShooterSubsystem       (concrete flywheel logic)

Configuration

Settings live in subsystems.json under shooterSubsystem:

Setting Units Purpose
enabled Master enable flag
maximumVelocityRpm RPM Maximum safe flywheel speed
maximumAccelerationRpmPerSecond RPM/s Acceleration limit for the trapezoidal ramp
velocityToleranceRpm RPM Window around target that counts as "ready"
settleTimeSeconds seconds How long RPM must stay in tolerance before ready signal
idleVelocityRpm RPM Default idle speed between shots
reverseVelocityRpm RPM Reverse speed for clearing jams
kS, kV, kA volts Feedforward gains
kP, kI, kD PID velocity controller gains

Motor-level settings live in motorConfig (primary) and followerMotorConfig (follower) blocks. Set the follower's enabled to false on robots with a single shooter motor.

Code structure

File Purpose
ShooterSubsystem.java Concrete subsystem managing flywheel velocity and ready-to-fire
commands/ShooterSubsystemCommandFactory.java Factory for spin-up, idle, stop, and SysId commands
commands/SpinUpShooterCommand.java Command that drives the flywheel to a target RPM
commands/IdleShooterCommand.java Default command holding idle RPM
config/ShooterSubsystemConfig.java Configuration bundle extending AbstractVelocitySubsystemConfig
shared/config/MotorConfig.java Motor-level config (CAN ID, inversion, current limits) (inherited)
devices/ShooterMotor.java Real-hardware motor wrapper for SparkMax
devices/ShooterSimMotor.java Simulation motor wrapper

Status / TODO

Done

  • Subsystem, commands, config, and device wrappers implemented.
  • Wired in RobotContainer and added to subsystems.json.
  • Operator right-bumper test binding for spin-up at 3000 RPM.
  • SysId sweep available on operator Y button.

TODO

  • Tune PID and feedforward gains on real hardware.
  • Add distance-based RPM lookup via supplier.
  • Wire reverse command to operator binding.