001// Copyright (c) Choreo contributors 002 003package choreo.auto; 004 005import choreo.Choreo; 006import choreo.Choreo.TrajectoryCache; 007import choreo.Choreo.TrajectoryLogger; 008import choreo.trajectory.SwerveSample; 009import choreo.trajectory.Trajectory; 010import choreo.trajectory.TrajectorySample; 011import edu.wpi.first.math.geometry.Pose2d; 012import edu.wpi.first.wpilibj.DriverStation; 013import edu.wpi.first.wpilibj.RobotBase; 014import edu.wpi.first.wpilibj.event.EventLoop; 015import edu.wpi.first.wpilibj2.command.Command; 016import edu.wpi.first.wpilibj2.command.Commands; 017import edu.wpi.first.wpilibj2.command.Subsystem; 018import edu.wpi.first.wpilibj2.command.button.Trigger; 019import java.util.HashMap; 020import java.util.List; 021import java.util.Optional; 022import java.util.function.BiConsumer; 023import java.util.function.BooleanSupplier; 024import java.util.function.Supplier; 025 026/** 027 * A factory used to create autonomous routines. 028 * 029 * <p>Here is an example of how to use this class to create an auto routine: 030 * 031 * <h2>Example using <code>Trigger</code>s</h2> 032 * 033 * <pre><code> 034 * public AutoRoutine shootThenMove(AutoFactory factory) { 035 * // Create a new auto routine to return 036 * var routine = factory.newRoutine(); 037 * 038 * // Create a trajectory that moves the robot 2 meters 039 * AutoTrajectory trajectory = factory.trajectory("move2meters", routine); 040 * 041 * // Will automatically run the shoot command when the auto routine is first polled 042 * routine.enabled().onTrue(shooter.shoot()); 043 * 044 * // Gets a trigger from the shooter to if the shooter has a note, and will run the trajectory 045 * // command when the shooter does not have a note 046 * routine.enabled().and(shooter.hasNote()).onFalse(trajectory.cmd()); 047 * 048 * return routine; 049 * } 050 * </code></pre> 051 * 052 * <h2>Example using <code>CommandGroup</code>s</h2> 053 * 054 * <pre><code> 055 * public Command shootThenMove(AutoFactory factory) { 056 * // Create a trajectory that moves the robot 2 meters 057 * Command trajectory = factory.trajectoryCommand("move2meters"); 058 * 059 * return shooter.shoot() 060 * .andThen(trajectory) 061 * .withName("ShootThenMove"); 062 * } 063 * </code></pre> 064 */ 065public class AutoFactory { 066 static final AutoRoutine VOID_ROUTINE = 067 new AutoRoutine("VOID-ROUTINE") { 068 private final EventLoop loop = new EventLoop(); 069 070 @Override 071 public Command cmd() { 072 return Commands.none().withName("VoidAutoRoutine"); 073 } 074 075 @Override 076 public Command cmd(BooleanSupplier _finishCondition) { 077 return cmd(); 078 } 079 080 @Override 081 public void poll() {} 082 083 @Override 084 public void reset() {} 085 086 @Override 087 public Trigger running() { 088 return new Trigger(loop, () -> false); 089 } 090 }; 091 092 /** A class used to bind commands to events in all trajectories created by this factory. */ 093 public static class AutoBindings { 094 private HashMap<String, Command> bindings = new HashMap<>(); 095 096 /** Default constructor. */ 097 public AutoBindings() {} 098 099 /** 100 * Binds a command to an event in all trajectories created by the factory using this bindings. 101 * 102 * @param name The name of the event to bind the command to. 103 * @param cmd The command to bind to the event. 104 * @return The bindings object for chaining. 105 */ 106 public AutoBindings bind(String name, Command cmd) { 107 bindings.put(name, cmd); 108 return this; 109 } 110 111 private void merge(AutoBindings other) { 112 if (other == null) { 113 return; 114 } 115 bindings.putAll(other.bindings); 116 } 117 118 /** 119 * Gets the bindings map. 120 * 121 * @return The bindings map. 122 */ 123 HashMap<String, Command> getBindings() { 124 return bindings; 125 } 126 } 127 128 private final TrajectoryCache trajectoryCache = new TrajectoryCache(); 129 private final Supplier<Pose2d> poseSupplier; 130 private final BiConsumer<Pose2d, ? extends TrajectorySample<?>> controller; 131 private final BooleanSupplier mirrorTrajectory; 132 private final Subsystem driveSubsystem; 133 private final AutoBindings bindings = new AutoBindings(); 134 private final Optional<TrajectoryLogger<? extends TrajectorySample<?>>> trajectoryLogger; 135 136 /** 137 * Its recommended to use the {@link Choreo#createAutoFactory} to create a new instance of this 138 * class. 139 * 140 * @param <SampleType> {@link Choreo#createAutoFactory} 141 * @param poseSupplier {@link Choreo#createAutoFactory} 142 * @param controller {@link Choreo#createAutoFactory} 143 * @param mirrorTrajectory {@link Choreo#createAutoFactory} 144 * @param driveSubsystem {@link Choreo#createAutoFactory} 145 * @param bindings {@link Choreo#createAutoFactory} 146 * @param trajectoryLogger {@link Choreo#createAutoFactory} 147 */ 148 public <SampleType extends TrajectorySample<SampleType>> AutoFactory( 149 Supplier<Pose2d> poseSupplier, 150 BiConsumer<Pose2d, SampleType> controller, 151 BooleanSupplier mirrorTrajectory, 152 Subsystem driveSubsystem, 153 AutoBindings bindings, 154 Optional<TrajectoryLogger<SampleType>> trajectoryLogger) { 155 this.poseSupplier = poseSupplier; 156 this.controller = controller; 157 this.mirrorTrajectory = mirrorTrajectory; 158 this.driveSubsystem = driveSubsystem; 159 this.bindings.merge(bindings); 160 this.trajectoryLogger = 161 trajectoryLogger.map(logger -> (TrajectoryLogger<? extends TrajectorySample<?>>) logger); 162 } 163 164 /** 165 * Creates a new {@link AutoRoutine}. 166 * 167 * @param name The name of the {@link AutoRoutine}. 168 * @return A new {@link AutoRoutine}. 169 * @see AutoRoutine 170 * @see #voidRoutine 171 */ 172 public AutoRoutine newRoutine(String name) { 173 // Clear cache in simulation to allow a form of "hot-reloading" trajectories 174 if (RobotBase.isSimulation()) { 175 clearCache(); 176 } 177 178 return new AutoRoutine(name); 179 } 180 181 /** 182 * An {@link AutoRoutine} that cannot have any side-effects, it stores no state and does nothing 183 * when polled. 184 * 185 * @return A void {@link AutoRoutine}. 186 * @see #newRoutine 187 */ 188 public AutoRoutine voidRoutine() { 189 return VOID_ROUTINE; 190 } 191 192 /** 193 * Creates a new auto trajectory to be used in an auto routine. 194 * 195 * @param trajectoryName The name of the trajectory to use. 196 * @param routine The {@link AutoRoutine} to register this trajectory under. 197 * @return A new auto trajectory. 198 */ 199 public AutoTrajectory trajectory(String trajectoryName, AutoRoutine routine) { 200 Optional<? extends Trajectory<?>> optTrajectory = 201 trajectoryCache.loadTrajectory(trajectoryName); 202 Trajectory<?> trajectory; 203 if (optTrajectory.isPresent()) { 204 trajectory = optTrajectory.get(); 205 } else { 206 DriverStation.reportError("Could not load trajectory: " + trajectoryName, false); 207 trajectory = new Trajectory<SwerveSample>(trajectoryName, List.of(), List.of(), List.of()); 208 } 209 return trajectory(trajectory, routine); 210 } 211 212 /** 213 * Creates a new auto trajectory to be used in an auto routine. 214 * 215 * @param trajectoryName The name of the trajectory to use. 216 * @param splitIndex The index of the split trajectory to use. 217 * @param routine The {@link AutoRoutine} to register this trajectory under. 218 * @return A new auto trajectory. 219 */ 220 public AutoTrajectory trajectory( 221 String trajectoryName, final int splitIndex, AutoRoutine routine) { 222 Optional<? extends Trajectory<?>> optTrajectory = 223 trajectoryCache.loadTrajectory(trajectoryName, splitIndex); 224 Trajectory<?> trajectory; 225 if (optTrajectory.isPresent()) { 226 trajectory = optTrajectory.get(); 227 } else { 228 DriverStation.reportError("Could not load trajectory: " + trajectoryName, false); 229 trajectory = new Trajectory<SwerveSample>(trajectoryName, List.of(), List.of(), List.of()); 230 } 231 return trajectory(trajectory, routine); 232 } 233 234 /** 235 * Creates a new auto trajectory to be used in an auto routine. 236 * 237 * @param <SampleType> The type of the trajectory samples. 238 * @param trajectory The trajectory to use. 239 * @param routine The {@link AutoRoutine} to register this trajectory under. 240 * @return A new auto trajectory. 241 */ 242 @SuppressWarnings("unchecked") 243 public <SampleType extends TrajectorySample<SampleType>> AutoTrajectory trajectory( 244 Trajectory<SampleType> trajectory, AutoRoutine routine) { 245 // type solidify everything 246 final Trajectory<SampleType> solidTrajectory = trajectory; 247 final BiConsumer<Pose2d, SampleType> solidController = 248 (BiConsumer<Pose2d, SampleType>) this.controller; 249 final Optional<TrajectoryLogger<SampleType>> solidLogger = 250 this.trajectoryLogger.map(logger -> (TrajectoryLogger<SampleType>) logger); 251 return new AutoTrajectory( 252 trajectory.name(), 253 solidTrajectory, 254 poseSupplier, 255 solidController, 256 mirrorTrajectory, 257 solidLogger, 258 driveSubsystem, 259 routine, 260 bindings); 261 } 262 263 /** 264 * Creates a new auto trajectory command to be used in an auto routine. 265 * 266 * <p><b>Important </b> 267 * 268 * <p>{@link #trajectoryCommand} and {@link #trajectory} methods should not be mixed in the same 269 * auto routine. {@link #trajectoryCommand} is used as an escape hatch for teams that don't need 270 * the benefits of the {@link #trajectory} method and its {@link Trigger} API. {@link 271 * #trajectoryCommand} does not invoke bindings added via calling {@link #bind} or {@link 272 * AutoBindings} passed into the factory constructor. 273 * 274 * @param trajectoryName The name of the trajectory to use. 275 * @return A new auto trajectory. 276 */ 277 public Command trajectoryCommand(String trajectoryName) { 278 return trajectory(trajectoryName, VOID_ROUTINE).cmd(); 279 } 280 281 /** 282 * Creates a new auto trajectory command to be used in an auto routine. 283 * 284 * <p><b>Important </b> 285 * 286 * <p>{@link #trajectoryCommand} and {@link #trajectory} methods should not be mixed in the same 287 * auto routine. {@link #trajectoryCommand} is used as an escape hatch for teams that don't need 288 * the benefits of the {@link #trajectory} method and its {@link Trigger} API. {@link 289 * #trajectoryCommand} does not invoke bindings added via calling {@link #bind} or {@link 290 * AutoBindings} passed into the factory constructor. 291 * 292 * @param trajectoryName The name of the trajectory to use. 293 * @param splitIndex The index of the split trajectory to use. 294 * @return A new auto trajectory. 295 */ 296 public Command trajectoryCommand(String trajectoryName, final int splitIndex) { 297 return trajectory(trajectoryName, splitIndex, VOID_ROUTINE).cmd(); 298 } 299 300 /** 301 * Creates a new auto trajectory command to be used in an auto routine. 302 * 303 * <p><b>Important </b> 304 * 305 * <p>{@link #trajectoryCommand} and {@link #trajectory} methods should not be mixed in the same 306 * auto routine. {@link #trajectoryCommand} is used as an escape hatch for teams that don't need 307 * the benefits of the {@link #trajectory} method and its {@link Trigger} API. {@link 308 * #trajectoryCommand} does not invoke bindings added via calling {@link #bind} or {@link 309 * AutoBindings} passed into the factory constructor. 310 * 311 * @param <SampleType> The type of the trajectory samples. 312 * @param trajectory The trajectory to use. 313 * @return A new auto trajectory. 314 */ 315 public <SampleType extends TrajectorySample<SampleType>> Command trajectoryCommand( 316 Trajectory<SampleType> trajectory) { 317 return trajectory(trajectory, VOID_ROUTINE).cmd(); 318 } 319 320 /** 321 * Creates an {@link AutoRoutine} with the name of the command. The command is the bound to the 322 * routine's enabled trigger. This is useful for adding a {@link Command} composition based auto 323 * to the {@link choreo.auto.AutoChooser}. 324 * 325 * @param cmd The command to bind to the routine. 326 * @return A new auto routine. 327 */ 328 public AutoRoutine commandAsAutoRoutine(Command cmd) { 329 AutoRoutine routine = newRoutine(cmd.getName()); 330 routine.running().onTrue(cmd); 331 return routine; 332 } 333 334 /** 335 * Binds a command to an event in all trajectories created after this point. 336 * 337 * @param name The name of the trajectory to bind the command to. 338 * @param cmd The command to bind to the trajectory. 339 */ 340 public void bind(String name, Command cmd) { 341 bindings.bind(name, cmd); 342 } 343 344 /** 345 * The {@link AutoFactory} caches trajectories with a {@link TrajectoryCache} to avoid reloading 346 * the same trajectory multiple times. This can have the side effect of keeping a single copy of 347 * every trajectory ever loaded in memory aslong as the factory is loaded. This method clears the 348 * cache of all trajectories. 349 * 350 * <p><b>Usage Note:</b> 351 * 352 * <p>Never clearing the cache is unlikely to have an impact on the robots performance on a rio 2 353 */ 354 public void clearCache() { 355 trajectoryCache.clear(); 356 } 357}