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