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