001// Copyright (c) Choreo contributors 002 003package choreo.auto; 004 005import static org.wpilib.util.ErrorMessages.requireNonNullParam; 006 007import choreo.Choreo.TrajectoryCache; 008import choreo.Choreo.TrajectoryLogger; 009import choreo.trajectory.SwerveSample; 010import choreo.trajectory.Trajectory; 011import choreo.trajectory.TrajectorySample; 012import choreo.util.ChoreoAllianceFlipUtil; 013import java.util.HashMap; 014import java.util.List; 015import java.util.Optional; 016import java.util.function.BooleanSupplier; 017import java.util.function.Consumer; 018import java.util.function.Function; 019import java.util.function.Supplier; 020import org.wpilib.command2.Command; 021import org.wpilib.command2.Commands; 022import org.wpilib.command2.Subsystem; 023import org.wpilib.command2.button.Trigger; 024import org.wpilib.driverstation.Alliance; 025import org.wpilib.driverstation.MatchState; 026import org.wpilib.framework.RobotBase; 027import org.wpilib.hardware.hal.HAL; 028import org.wpilib.math.geometry.Pose2d; 029 030/** 031 * A factory used to create {@link AutoRoutine}s and {@link AutoTrajectory}s. 032 * 033 * @see <a href="https://choreo.autos/choreolib/auto-routines">Auto Routine Docs</a> 034 */ 035public class AutoFactory { 036 static record AllianceContext( 037 boolean useAllianceFlipping, Supplier<Optional<Alliance>> allianceGetter) { 038 boolean allianceKnownOrIgnored() { 039 return allianceGetter.get().isPresent() || !useAllianceFlipping; 040 } 041 042 boolean doFlip() { 043 return useAllianceFlipping 044 && allianceGetter 045 .get() 046 .orElseThrow( 047 () -> new RuntimeException("Flip check was called with an unknown alliance")) 048 .equals(Alliance.RED); 049 } 050 051 Optional<Alliance> alliance() { 052 return allianceGetter.get(); 053 } 054 055 Supplier<Optional<Pose2d>> getFlippedPose(Optional<Pose2d> bluePose) { 056 return ChoreoAllianceFlipUtil.optionalFlippedPose2d( 057 bluePose, this::alliance, useAllianceFlipping); 058 } 059 } 060 061 /** A class used to bind commands to events in all trajectories created by this factory. */ 062 static class AutoBindings { 063 private HashMap<String, Command> bindings = new HashMap<>(); 064 065 /** Default constructor. */ 066 public AutoBindings() {} 067 068 /** 069 * Binds a command to an event in all trajectories created by the factory using this bindings. 070 * 071 * @param name The name of the event to bind the command to. 072 * @param cmd The command to bind to the event. 073 * @return The bindings object for chaining. 074 */ 075 public AutoBindings bind(String name, Command cmd) { 076 bindings.put(name, cmd); 077 return this; 078 } 079 080 /** 081 * Gets the bindings map. 082 * 083 * @return The bindings map. 084 */ 085 HashMap<String, Command> getBindings() { 086 return bindings; 087 } 088 } 089 090 private final TrajectoryCache trajectoryCache = new TrajectoryCache(); 091 private final Supplier<Pose2d> poseSupplier; 092 private final Consumer<Pose2d> resetOdometry; 093 private final Consumer<? extends TrajectorySample<?>> controller; 094 private final AllianceContext allianceCtx; 095 private final Subsystem driveSubsystem; 096 private final AutoBindings bindings = new AutoBindings(); 097 private final TrajectoryLogger<? extends TrajectorySample<?>> trajectoryLogger; 098 private final AutoRoutine voidRoutine; 099 100 /** 101 * Create a factory that can be used to create {@link AutoRoutine} and {@link AutoTrajectory}. 102 * 103 * @param <SampleType> The type of samples in the trajectory. 104 * @param poseSupplier A function that returns the current field-relative {@link Pose2d} of the 105 * robot. 106 * @param resetOdometry A function that receives a field-relative {@link Pose2d} to reset the 107 * robot's odometry to. 108 * @param controller A function that receives the current {@link SampleType} and controls the 109 * robot. 110 * @param useAllianceFlipping If this is true, when on the red alliance, the path will be mirrored 111 * to the opposite side, while keeping the same coordinate system origin. 112 * @param driveSubsystem The drive {@link Subsystem} to require for {@link AutoTrajectory} {@link 113 * Command}s. 114 * @param trajectoryLogger A {@link TrajectoryLogger} to log {@link Trajectory} as they start and 115 * finish. 116 * @see AutoChooser using this factory with AutoChooser to generate auto routines. 117 */ 118 public <SampleType extends TrajectorySample<SampleType>> AutoFactory( 119 Supplier<Pose2d> poseSupplier, 120 Consumer<Pose2d> resetOdometry, 121 Consumer<SampleType> controller, 122 boolean useAllianceFlipping, 123 Subsystem driveSubsystem, 124 TrajectoryLogger<SampleType> trajectoryLogger) { 125 requireNonNullParam(poseSupplier, "poseSupplier", "AutoFactory"); 126 requireNonNullParam(resetOdometry, "resetOdometry", "AutoFactory"); 127 requireNonNullParam(controller, "controller", "AutoFactory"); 128 requireNonNullParam(driveSubsystem, "driveSubsystem", "AutoFactory"); 129 requireNonNullParam(useAllianceFlipping, "useAllianceFlipping", "AutoFactory"); 130 131 this.poseSupplier = poseSupplier; 132 this.resetOdometry = resetOdometry; 133 this.controller = controller; 134 this.driveSubsystem = driveSubsystem; 135 this.allianceCtx = new AllianceContext(useAllianceFlipping, MatchState::getAlliance); 136 this.trajectoryLogger = trajectoryLogger; 137 HAL.reportUsage("ChoreoTrigger", 1, "AutoFactory"); 138 139 voidRoutine = 140 new AutoRoutine(this, "VOID-ROUTINE", allianceCtx) { 141 @Override 142 public Command cmd() { 143 return Commands.none().withName("VoidAutoRoutine"); 144 } 145 146 @Override 147 public Command cmd(BooleanSupplier _finishCondition) { 148 return cmd(); 149 } 150 151 @Override 152 public void poll() {} 153 154 @Override 155 public void reset() {} 156 157 @Override 158 public Trigger active() { 159 return new Trigger(this.loop(), () -> true); 160 } 161 }; 162 } 163 164 /** 165 * Create a factory that can be used to create {@link AutoRoutine} and {@link AutoTrajectory}. 166 * 167 * @param <ST> {@link choreo.trajectory.DifferentialSample} or {@link 168 * choreo.trajectory.SwerveSample} 169 * @param poseSupplier A function that returns the current field-relative {@link Pose2d} of the 170 * robot. 171 * @param resetOdometry A function that receives a field-relative {@link Pose2d} to reset the 172 * robot's odometry to. 173 * @param controller A function that receives the current {@link ST} and controls the robot. 174 * @param useAllianceFlipping If this returns true, when on the red alliance, the path will be 175 * mirrored to the opposite side, while keeping the same coordinate system origin. 176 * @param driveSubsystem The drive {@link Subsystem} to require for {@link AutoTrajectory} {@link 177 * Command}s. 178 * @see AutoChooser using this factory with AutoChooser to generate auto routines. 179 */ 180 public <ST extends TrajectorySample<ST>> AutoFactory( 181 Supplier<Pose2d> poseSupplier, 182 Consumer<Pose2d> resetOdometry, 183 Consumer<ST> controller, 184 boolean useAllianceFlipping, 185 Subsystem driveSubsystem) { 186 this( 187 poseSupplier, 188 resetOdometry, 189 controller, 190 useAllianceFlipping, 191 driveSubsystem, 192 (sample, isStart) -> {}); 193 } 194 195 /** 196 * Creates a new {@link AutoRoutine}. 197 * 198 * @param name The name of the {@link AutoRoutine}. 199 * @return A new {@link AutoRoutine}. 200 */ 201 public AutoRoutine newRoutine(String name) { 202 // Clear cache in simulation to allow a form of "hot-reloading" trajectories 203 if (RobotBase.isSimulation()) { 204 trajectoryCache.clear(); 205 } 206 207 return new AutoRoutine(this, name, allianceCtx); 208 } 209 210 /** 211 * A package protected method to create a new {@link AutoTrajectory} to be used in an {@link 212 * AutoRoutine}. 213 * 214 * @see AutoRoutine#trajectory(String) 215 */ 216 AutoTrajectory trajectory(String trajectoryName, AutoRoutine routine, boolean useBindings) { 217 Optional<? extends Trajectory<?>> optTrajectory = 218 trajectoryCache.loadTrajectory(trajectoryName); 219 Trajectory<?> trajectory; 220 if (optTrajectory.isPresent()) { 221 trajectory = optTrajectory.get(); 222 } else { 223 trajectory = new Trajectory<SwerveSample>(trajectoryName, List.of(), List.of(), List.of()); 224 } 225 return trajectory(trajectory, routine, useBindings); 226 } 227 228 /** 229 * A package protected method to create a new {@link AutoTrajectory} to be used in an {@link 230 * AutoRoutine}. 231 * 232 * @see AutoRoutine#trajectory(String, int) 233 */ 234 AutoTrajectory trajectory( 235 String trajectoryName, final int splitIndex, AutoRoutine routine, boolean useBindings) { 236 Optional<? extends Trajectory<?>> optTrajectory = 237 trajectoryCache.loadTrajectory(trajectoryName, splitIndex); 238 Trajectory<?> trajectory; 239 if (optTrajectory.isPresent()) { 240 trajectory = optTrajectory.get(); 241 } else { 242 trajectory = new Trajectory<SwerveSample>(trajectoryName, List.of(), List.of(), List.of()); 243 } 244 return trajectory(trajectory, routine, useBindings); 245 } 246 247 /** 248 * A package protected method to create a new {@link AutoTrajectory} to be used in an {@link 249 * AutoRoutine}. 250 * 251 * @see AutoRoutine#trajectory(Trajectory) 252 */ 253 <ST extends TrajectorySample<ST>> AutoTrajectory trajectory( 254 Trajectory<ST> trajectory, AutoRoutine routine, boolean useBindings) { 255 return trajectory(trajectory, routine, useBindings, Function.identity()); 256 } 257 258 /** 259 * A package protected method to create a new {@link AutoTrajectory} to be used in an {@link 260 * AutoRoutine}. 261 * 262 * @see AutoRoutine#trajectory(Trajectory) 263 */ 264 @SuppressWarnings("unchecked") 265 <ST extends TrajectorySample<ST>> AutoTrajectory trajectory( 266 Trajectory<ST> trajectory, 267 AutoRoutine routine, 268 boolean useBindings, 269 Function<Trajectory<ST>, Trajectory<ST>> trajectoryTransform) { 270 // type solidify everything 271 final Trajectory<ST> solidTrajectory = trajectoryTransform.apply(trajectory); 272 final Consumer<ST> solidController = (Consumer<ST>) this.controller; 273 return new AutoTrajectory( 274 trajectory.name(), 275 solidTrajectory, 276 poseSupplier, 277 resetOdometry, 278 solidController, 279 allianceCtx, 280 (TrajectoryLogger<ST>) trajectoryLogger, 281 driveSubsystem, 282 routine, 283 useBindings ? bindings : new AutoBindings()); 284 } 285 286 /** 287 * Warms up Choreo to ensure that there is no delay at the start of auto. It is recommended to 288 * schedule this command in your Robot constructor, like so: 289 * 290 * <pre><code> 291 * CommandScheduler.getInstance().schedule(autoFactory.warmupCmd()); 292 * </code></pre> 293 * 294 * @return A command that warms up Choreo's autonomous functionality. 295 */ 296 public Command warmupCmd() { 297 var autoTraj = trajectory("", voidRoutine, false); 298 autoTraj.suppressWarnings(); 299 return autoTraj.cmd().ignoringDisable(true).withTimeout(0.5).withName("Choreo Warmup Command"); 300 } 301 302 /** 303 * Creates a new {@link AutoTrajectory} command to be used in an auto routine. 304 * 305 * <p><b>Important </b> 306 * 307 * <p>{@link #trajectoryCmd} and {@link #trajectory} methods should not be mixed in the same auto 308 * routine. {@link #trajectoryCmd} is used as an escape hatch for teams that don't need the 309 * benefits of the {@link #trajectory} method and its {@link Trigger} API. {@link #trajectoryCmd} 310 * does not invoke bindings added via calling {@link #bind} or {@link AutoBindings} passed into 311 * the factory constructor. 312 * 313 * @param trajectoryName The name of the trajectory to use. 314 * @return A new {@link AutoTrajectory}. 315 */ 316 public Command trajectoryCmd(String trajectoryName) { 317 return trajectory(trajectoryName, voidRoutine, false).cmd(); 318 } 319 320 /** 321 * Creates a new {@link AutoTrajectory} command to be used in an auto routine. 322 * 323 * <p><b>Important </b> 324 * 325 * <p>{@link #trajectoryCmd} and {@link #trajectory} methods should not be mixed in the same auto 326 * routine. {@link #trajectoryCmd} is used as an escape hatch for teams that don't need the 327 * benefits of the {@link #trajectory} method and its {@link Trigger} API. {@link #trajectoryCmd} 328 * does not invoke bindings added via calling {@link #bind} or {@link AutoBindings} passed into 329 * the factory constructor. 330 * 331 * @param trajectoryName The name of the trajectory to use. 332 * @param splitIndex The index of the split trajectory to use. 333 * @param transform A function that takes in the loaded trajectory and applies a transformation to 334 * it, such as left-to-right mirroring. 335 * @return A new {@link AutoTrajectory}. 336 */ 337 public Command trajectoryCmd( 338 String trajectoryName, 339 final int splitIndex, 340 Function<AutoTrajectory, AutoTrajectory> transform) { 341 return transform.apply(trajectory(trajectoryName, splitIndex, voidRoutine, false)).cmd(); 342 } 343 344 /** 345 * Creates a new {@link AutoTrajectory} command to be used in an auto routine. 346 * 347 * <p><b>Important </b> 348 * 349 * <p>{@link #trajectoryCmd} and {@link #trajectory} methods should not be mixed in the same auto 350 * routine. {@link #trajectoryCmd} is used as an escape hatch for teams that don't need the 351 * benefits of the {@link #trajectory} method and its {@link Trigger} API. {@link #trajectoryCmd} 352 * does not invoke bindings added via calling {@link #bind} or {@link AutoBindings} passed into 353 * the factory constructor. 354 * 355 * @param trajectoryName The name of the trajectory to use. 356 * @param transform A function that takes in the loaded trajectory and applies a transformation to 357 * it, such as left-to-right mirroring. 358 * @return A new {@link AutoTrajectory}. 359 */ 360 public Command trajectoryCmd( 361 String trajectoryName, Function<AutoTrajectory, AutoTrajectory> transform) { 362 return transform.apply(trajectory(trajectoryName, voidRoutine, false)).cmd(); 363 } 364 365 /** 366 * Creates a new {@link AutoTrajectory} command to be used in an auto routine. 367 * 368 * <p><b>Important </b> 369 * 370 * <p>{@link #trajectoryCmd} and {@link #trajectory} methods should not be mixed in the same auto 371 * routine. {@link #trajectoryCmd} is used as an escape hatch for teams that don't need the 372 * benefits of the {@link #trajectory} method and its {@link Trigger} API. {@link #trajectoryCmd} 373 * does not invoke bindings added via calling {@link #bind} or {@link AutoBindings} passed into 374 * the factory constructor. 375 * 376 * @param trajectoryName The name of the trajectory to use. 377 * @param splitIndex The index of the split trajectory to use. 378 * @return A new {@link AutoTrajectory}. 379 */ 380 public Command trajectoryCmd(String trajectoryName, final int splitIndex) { 381 return trajectory(trajectoryName, splitIndex, voidRoutine, false).cmd(); 382 } 383 384 /** 385 * Creates a new {@link AutoTrajectory} command to be used in an auto routine. 386 * 387 * <p><b>Important </b> 388 * 389 * <p>{@link #trajectoryCmd} and {@link #trajectory} methods should not be mixed in the same auto 390 * routine. {@link #trajectoryCmd} is used as an escape hatch for teams that don't need the 391 * benefits of the {@link #trajectory} method and its {@link Trigger} API. {@link #trajectoryCmd} 392 * does not invoke bindings added via calling {@link #bind} or {@link AutoBindings} passed into 393 * the factory constructor. 394 * 395 * @param <ST> {@link choreo.trajectory.DifferentialSample} or {@link 396 * choreo.trajectory.SwerveSample} 397 * @param trajectory The trajectory to use. 398 * @return A new {@link AutoTrajectory}. 399 */ 400 public <ST extends TrajectorySample<ST>> Command trajectoryCmd(Trajectory<ST> trajectory) { 401 return trajectory(trajectory, voidRoutine, false).cmd(); 402 } 403 404 /** 405 * Creates a command that resets the robot's odometry to the start of a trajectory. 406 * 407 * @param trajectoryName The name of the trajectory to use. 408 * @return A command that resets the robot's odometry. 409 */ 410 public Command resetOdometry(String trajectoryName) { 411 return trajectory(trajectoryName, voidRoutine, false).resetOdometry(); 412 } 413 414 /** 415 * Creates a command that resets the robot's odometry to the start of a trajectory. 416 * 417 * @param trajectoryName The name of the trajectory to use. 418 * @param splitIndex The index of the split trajectory to use. 419 * @return A command that resets the robot's odometry. 420 */ 421 public Command resetOdometry(String trajectoryName, final int splitIndex) { 422 return trajectory(trajectoryName, splitIndex, voidRoutine, false).resetOdometry(); 423 } 424 425 /** 426 * Creates a command that resets the robot's odometry to the start of a trajectory. 427 * 428 * @param <ST> {@link choreo.trajectory.DifferentialSample} or {@link 429 * choreo.trajectory.SwerveSample} 430 * @param trajectory The trajectory to use. 431 * @return A command that resets the robot's odometry. 432 */ 433 public <ST extends TrajectorySample<ST>> Command resetOdometry(Trajectory<ST> trajectory) { 434 return trajectory(trajectory, voidRoutine, false).resetOdometry(); 435 } 436 437 /** 438 * Creates a command that resets the robot's odometry to the supplied pose 439 * 440 * @param pose A function that is called when the command is run. It returns an <code> 441 * Optional<Pose2d></code> of the robot's desired odometry position. 442 * @return A command that resets the robot's odometry to the supplied pose, or does nothing if the 443 * supplied Optional is empty. 444 */ 445 public Command resetOdometry(Supplier<Optional<Pose2d>> pose) { 446 return driveSubsystem.runOnce(() -> pose.get().ifPresent(resetOdometry)); 447 } 448 449 /** 450 * Creates a command that resets the robot's odometry to the given pose 451 * 452 * @param pose An <code>Optional<Pose2d></code> of the robot's desired odometry position. 453 * @param doFlipForAlliance True if the given pose still needs to be flipped according to the 454 * alliance (usually true). False if it is an absolute field position. 455 * @return A command that resets the robot's odometry to the given pose (flipped as directed), or 456 * does nothing if the supplied Optional is empty. 457 */ 458 public Command resetOdometry(Optional<Pose2d> pose, boolean doFlipForAlliance) { 459 if (pose.isEmpty()) { 460 return driveSubsystem.runOnce( 461 () -> {}); // equivalent to Commands.none() requiring driveSubsystem. 462 } 463 Supplier<Optional<Pose2d>> supplier = 464 doFlipForAlliance ? allianceCtx.getFlippedPose(pose) : (() -> pose); 465 return resetOdometry(supplier); 466 } 467 468 /** 469 * Binds a command to an event in all trajectories created after this point. 470 * 471 * @param name The name of the trajectory to bind the command to. 472 * @param cmd The command to bind to the trajectory. 473 * @return The AutoFactory the method was called from. 474 */ 475 public AutoFactory bind(String name, Command cmd) { 476 bindings.bind(name, cmd); 477 return this; 478 } 479 480 /** 481 * The {@link AutoFactory} caches trajectories with a {@link TrajectoryCache} to avoid reloading 482 * the same trajectory multiple times. 483 * 484 * @return The trajectory cache. 485 */ 486 public TrajectoryCache cache() { 487 return trajectoryCache; 488 } 489}