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&lt;Pose2d&gt;</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&lt;Pose2d&gt;</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}