001// Copyright (c) Choreo contributors
002
003package choreo.auto;
004
005import edu.wpi.first.networktables.NetworkTable;
006import edu.wpi.first.networktables.NetworkTableInstance;
007import edu.wpi.first.networktables.StringArrayEntry;
008import edu.wpi.first.networktables.StringEntry;
009import edu.wpi.first.wpilibj.DriverStation;
010import edu.wpi.first.wpilibj.IterativeRobotBase;
011import java.util.HashMap;
012import java.util.Map;
013import java.util.function.Function;
014
015/**
016 * An auto chooser that allows for the selection of auto routines at runtime.
017 *
018 * <p>This chooser takes a lazy loading approach to auto routines, only generating the auto routine
019 * when it is selected. This approach has the benefit of not loading all autos on startup, but also
020 * not loading the auto during auto start causing a delay.
021 *
022 * <p>Once the {@link AutoChooser} is made you can add auto routines to it using the {@link
023 * #addAutoRoutine(String, AutoRoutineGenerator)} method. Unlike {@code SendableChooser} this
024 * chooser has to be updated every cycle by calling the {@link #update()} method in your {@link
025 * IterativeRobotBase#robotPeriodic()}.
026 *
027 * <p>You can retrieve the {@link AutoRoutine} that is currently selected by calling the {@link
028 * #getSelectedAutoRoutine()} method.
029 */
030public class AutoChooser {
031  /** A function that generates an {@link AutoRoutine} from an {@link AutoFactory}. */
032  public static interface AutoRoutineGenerator extends Function<AutoFactory, AutoRoutine> {
033    /** A generator that returns an auto routine that does nothing */
034    static final AutoRoutineGenerator NONE = factory -> AutoFactory.VOID_ROUTINE;
035  }
036
037  private static final String NONE_NAME = "Nothing";
038
039  private final HashMap<String, AutoRoutineGenerator> autoRoutines =
040      new HashMap<>(Map.of(NONE_NAME, AutoRoutineGenerator.NONE));
041
042  private final StringEntry selected, active;
043  private final StringArrayEntry options;
044
045  private final AutoFactory factory;
046
047  private String lastAutoRoutineName = NONE_NAME;
048  private AutoRoutine lastAutoRoutine = AutoRoutineGenerator.NONE.apply(null);
049
050  /**
051   * Create a new auto chooser.
052   *
053   * @param factory The auto factory to use for auto routine generation.
054   * @param tableName The name of the network table to use for the chooser, passing in an empty
055   *     string will put this chooser at the root of the network tables.
056   */
057  public AutoChooser(AutoFactory factory, String tableName) {
058    this.factory = factory;
059
060    String path = NetworkTable.normalizeKey(tableName, true) + "/AutoChooser";
061    NetworkTable table = NetworkTableInstance.getDefault().getTable(path);
062
063    selected = table.getStringTopic("selected").getEntry(NONE_NAME);
064    table.getStringTopic(".type").publish().set("String Chooser");
065    table.getStringTopic("default").publish().set(NONE_NAME);
066    active = table.getStringTopic("active").getEntry(NONE_NAME);
067    options =
068        table.getStringArrayTopic("options").getEntry(autoRoutines.keySet().toArray(new String[0]));
069  }
070
071  /**
072   * Update the auto chooser.
073   *
074   * <p>This method should be called every cycle in the {@link IterativeRobotBase#robotPeriodic()}.
075   * It will check if the selected auto routine has changed and update the active auto routine.
076   */
077  public void update() {
078    if (DriverStation.isDisabled() || IterativeRobotBase.isSimulation()) {
079      String selectStr = selected.get();
080      if (selectStr.equals(lastAutoRoutineName)) return;
081      if (!autoRoutines.containsKey(selectStr)) {
082        selected.set(NONE_NAME);
083        selectStr = NONE_NAME;
084        DriverStation.reportError("Selected an auto that isn't an option", false);
085      }
086      lastAutoRoutineName = selectStr;
087      lastAutoRoutine = autoRoutines.get(lastAutoRoutineName).apply(this.factory);
088      active.set(lastAutoRoutineName);
089    }
090  }
091
092  /**
093   * Add an auto routine to the chooser.
094   *
095   * <p>An auto routine is a function that takes an AutoFactory and returns a Command. These
096   * functions can be static, a lambda or belong to a local variable.
097   *
098   * <p>A good paradigm is making an `AutoRoutines` class that has a reference to all your
099   * subsystems and has helper methods for auto commands inside it. Then you crate methods inside
100   * that class that take an `AutoFactory` and return a `Command`.
101   *
102   * @param name The name of the auto routine.
103   * @param generator The function that generates the auto routine.
104   */
105  public void addAutoRoutine(String name, AutoRoutineGenerator generator) {
106    autoRoutines.put(name, generator);
107    options.set(autoRoutines.keySet().toArray(new String[0]));
108  }
109
110  /**
111   * Get the currently selected auto routine.
112   *
113   * @return The currently selected auto routine.
114   */
115  public AutoRoutine getSelectedAutoRoutine() {
116    return lastAutoRoutine;
117  }
118}