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}