001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.wpilibj;
006
007import edu.wpi.first.hal.DriverStationJNI;
008import edu.wpi.first.hal.FRCNetComm.tInstances;
009import edu.wpi.first.hal.FRCNetComm.tResourceType;
010import edu.wpi.first.hal.HAL;
011import edu.wpi.first.networktables.NetworkTableInstance;
012import edu.wpi.first.wpilibj.livewindow.LiveWindow;
013import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard;
014import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
015import java.util.ConcurrentModificationException;
016
017/**
018 * IterativeRobotBase implements a specific type of robot program framework, extending the RobotBase
019 * class.
020 *
021 * <p>The IterativeRobotBase class does not implement startCompetition(), so it should not be used
022 * by teams directly.
023 *
024 * <p>This class provides the following functions which are called by the main loop,
025 * startCompetition(), at the appropriate times:
026 *
027 * <p>robotInit() -- provide for initialization at robot power-on
028 *
029 * <p>driverStationConnected() -- provide for initialization the first time the DS is connected
030 *
031 * <p>init() functions -- each of the following functions is called once when the appropriate mode
032 * is entered:
033 *
034 * <ul>
035 *   <li>disabledInit() -- called each and every time disabled is entered from another mode
036 *   <li>autonomousInit() -- called each and every time autonomous is entered from another mode
037 *   <li>teleopInit() -- called each and every time teleop is entered from another mode
038 *   <li>testInit() -- called each and every time test is entered from another mode
039 * </ul>
040 *
041 * <p>periodic() functions -- each of these functions is called on an interval:
042 *
043 * <ul>
044 *   <li>robotPeriodic()
045 *   <li>disabledPeriodic()
046 *   <li>autonomousPeriodic()
047 *   <li>teleopPeriodic()
048 *   <li>testPeriodic()
049 * </ul>
050 *
051 * <p>exit() functions -- each of the following functions is called once when the appropriate mode
052 * is exited:
053 *
054 * <ul>
055 *   <li>disabledExit() -- called each and every time disabled is exited
056 *   <li>autonomousExit() -- called each and every time autonomous is exited
057 *   <li>teleopExit() -- called each and every time teleop is exited
058 *   <li>testExit() -- called each and every time test is exited
059 * </ul>
060 */
061public abstract class IterativeRobotBase extends RobotBase {
062  private enum Mode {
063    kNone,
064    kDisabled,
065    kAutonomous,
066    kTeleop,
067    kTest
068  }
069
070  private final DSControlWord m_word = new DSControlWord();
071  private Mode m_lastMode = Mode.kNone;
072  private final double m_period;
073  private final Watchdog m_watchdog;
074  private boolean m_ntFlushEnabled = true;
075  private boolean m_lwEnabledInTest;
076  private boolean m_calledDsConnected;
077
078  /**
079   * Constructor for IterativeRobotBase.
080   *
081   * @param period Period in seconds.
082   */
083  protected IterativeRobotBase(double period) {
084    m_period = period;
085    m_watchdog = new Watchdog(period, this::printLoopOverrunMessage);
086  }
087
088  /** Provide an alternate "main loop" via startCompetition(). */
089  @Override
090  public abstract void startCompetition();
091
092  /* ----------- Overridable initialization code ----------------- */
093
094  /**
095   * Robot-wide initialization code should go here.
096   *
097   * <p>Users should override this method for default Robot-wide initialization which will be called
098   * when the robot is first powered on. It will be called exactly one time.
099   *
100   * <p>Note: This method is functionally identical to the class constructor so that should be used
101   * instead.
102   */
103  public void robotInit() {}
104
105  /**
106   * Code that needs to know the DS state should go here.
107   *
108   * <p>Users should override this method for initialization that needs to occur after the DS is
109   * connected, such as needing the alliance information.
110   */
111  public void driverStationConnected() {}
112
113  /**
114   * Robot-wide simulation initialization code should go here.
115   *
116   * <p>Users should override this method for default Robot-wide simulation related initialization
117   * which will be called when the robot is first started. It will be called exactly one time after
118   * RobotInit is called only when the robot is in simulation.
119   */
120  public void simulationInit() {}
121
122  /**
123   * Initialization code for disabled mode should go here.
124   *
125   * <p>Users should override this method for initialization code which will be called each time the
126   * robot enters disabled mode.
127   */
128  public void disabledInit() {}
129
130  /**
131   * Initialization code for autonomous mode should go here.
132   *
133   * <p>Users should override this method for initialization code which will be called each time the
134   * robot enters autonomous mode.
135   */
136  public void autonomousInit() {}
137
138  /**
139   * Initialization code for teleop mode should go here.
140   *
141   * <p>Users should override this method for initialization code which will be called each time the
142   * robot enters teleop mode.
143   */
144  public void teleopInit() {}
145
146  /**
147   * Initialization code for test mode should go here.
148   *
149   * <p>Users should override this method for initialization code which will be called each time the
150   * robot enters test mode.
151   */
152  public void testInit() {}
153
154  /* ----------- Overridable periodic code ----------------- */
155
156  private boolean m_rpFirstRun = true;
157
158  /** Periodic code for all robot modes should go here. */
159  public void robotPeriodic() {
160    if (m_rpFirstRun) {
161      System.out.println("Default robotPeriodic() method... Override me!");
162      m_rpFirstRun = false;
163    }
164  }
165
166  private boolean m_spFirstRun = true;
167
168  /**
169   * Periodic simulation code should go here.
170   *
171   * <p>This function is called in a simulated robot after user code executes.
172   */
173  public void simulationPeriodic() {
174    if (m_spFirstRun) {
175      System.out.println("Default simulationPeriodic() method... Override me!");
176      m_spFirstRun = false;
177    }
178  }
179
180  private boolean m_dpFirstRun = true;
181
182  /** Periodic code for disabled mode should go here. */
183  public void disabledPeriodic() {
184    if (m_dpFirstRun) {
185      System.out.println("Default disabledPeriodic() method... Override me!");
186      m_dpFirstRun = false;
187    }
188  }
189
190  private boolean m_apFirstRun = true;
191
192  /** Periodic code for autonomous mode should go here. */
193  public void autonomousPeriodic() {
194    if (m_apFirstRun) {
195      System.out.println("Default autonomousPeriodic() method... Override me!");
196      m_apFirstRun = false;
197    }
198  }
199
200  private boolean m_tpFirstRun = true;
201
202  /** Periodic code for teleop mode should go here. */
203  public void teleopPeriodic() {
204    if (m_tpFirstRun) {
205      System.out.println("Default teleopPeriodic() method... Override me!");
206      m_tpFirstRun = false;
207    }
208  }
209
210  private boolean m_tmpFirstRun = true;
211
212  /** Periodic code for test mode should go here. */
213  public void testPeriodic() {
214    if (m_tmpFirstRun) {
215      System.out.println("Default testPeriodic() method... Override me!");
216      m_tmpFirstRun = false;
217    }
218  }
219
220  /**
221   * Exit code for disabled mode should go here.
222   *
223   * <p>Users should override this method for code which will be called each time the robot exits
224   * disabled mode.
225   */
226  public void disabledExit() {}
227
228  /**
229   * Exit code for autonomous mode should go here.
230   *
231   * <p>Users should override this method for code which will be called each time the robot exits
232   * autonomous mode.
233   */
234  public void autonomousExit() {}
235
236  /**
237   * Exit code for teleop mode should go here.
238   *
239   * <p>Users should override this method for code which will be called each time the robot exits
240   * teleop mode.
241   */
242  public void teleopExit() {}
243
244  /**
245   * Exit code for test mode should go here.
246   *
247   * <p>Users should override this method for code which will be called each time the robot exits
248   * test mode.
249   */
250  public void testExit() {}
251
252  /**
253   * Enables or disables flushing NetworkTables every loop iteration. By default, this is enabled.
254   *
255   * @param enabled True to enable, false to disable
256   * @deprecated Deprecated without replacement.
257   */
258  @Deprecated(forRemoval = true, since = "2025")
259  public void setNetworkTablesFlushEnabled(boolean enabled) {
260    m_ntFlushEnabled = enabled;
261  }
262
263  private boolean m_reportedLw;
264
265  /**
266   * Sets whether LiveWindow operation is enabled during test mode. Calling
267   *
268   * @param testLW True to enable, false to disable. Defaults to false.
269   * @throws ConcurrentModificationException if this is called during test mode.
270   */
271  public void enableLiveWindowInTest(boolean testLW) {
272    if (isTestEnabled()) {
273      throw new ConcurrentModificationException("Can't configure test mode while in test mode!");
274    }
275    if (!m_reportedLw && testLW) {
276      HAL.report(tResourceType.kResourceType_SmartDashboard, tInstances.kSmartDashboard_LiveWindow);
277      m_reportedLw = true;
278    }
279    m_lwEnabledInTest = testLW;
280  }
281
282  /**
283   * Whether LiveWindow operation is enabled during test mode.
284   *
285   * @return whether LiveWindow should be enabled in test mode.
286   */
287  public boolean isLiveWindowEnabledInTest() {
288    return m_lwEnabledInTest;
289  }
290
291  /**
292   * Gets time period between calls to Periodic() functions.
293   *
294   * @return The time period between calls to Periodic() functions.
295   */
296  public double getPeriod() {
297    return m_period;
298  }
299
300  /** Loop function. */
301  protected void loopFunc() {
302    DriverStation.refreshData();
303    m_watchdog.reset();
304
305    m_word.refresh();
306
307    // Get current mode
308    Mode mode = Mode.kNone;
309    if (m_word.isDisabled()) {
310      mode = Mode.kDisabled;
311    } else if (m_word.isAutonomous()) {
312      mode = Mode.kAutonomous;
313    } else if (m_word.isTeleop()) {
314      mode = Mode.kTeleop;
315    } else if (m_word.isTest()) {
316      mode = Mode.kTest;
317    }
318
319    if (!m_calledDsConnected && m_word.isDSAttached()) {
320      m_calledDsConnected = true;
321      driverStationConnected();
322    }
323
324    // If mode changed, call mode exit and entry functions
325    if (m_lastMode != mode) {
326      // Call last mode's exit function
327      switch (m_lastMode) {
328        case kDisabled -> disabledExit();
329        case kAutonomous -> autonomousExit();
330        case kTeleop -> teleopExit();
331        case kTest -> {
332          if (m_lwEnabledInTest) {
333            LiveWindow.setEnabled(false);
334            Shuffleboard.disableActuatorWidgets();
335          }
336          testExit();
337        }
338        default -> {
339          // NOP
340        }
341      }
342
343      // Call current mode's entry function
344      switch (mode) {
345        case kDisabled -> {
346          disabledInit();
347          m_watchdog.addEpoch("disabledInit()");
348        }
349        case kAutonomous -> {
350          autonomousInit();
351          m_watchdog.addEpoch("autonomousInit()");
352        }
353        case kTeleop -> {
354          teleopInit();
355          m_watchdog.addEpoch("teleopInit()");
356        }
357        case kTest -> {
358          if (m_lwEnabledInTest) {
359            LiveWindow.setEnabled(true);
360            Shuffleboard.enableActuatorWidgets();
361          }
362          testInit();
363          m_watchdog.addEpoch("testInit()");
364        }
365        default -> {
366          // NOP
367        }
368      }
369
370      m_lastMode = mode;
371    }
372
373    // Call the appropriate function depending upon the current robot mode
374    switch (mode) {
375      case kDisabled -> {
376        DriverStationJNI.observeUserProgramDisabled();
377        disabledPeriodic();
378        m_watchdog.addEpoch("disabledPeriodic()");
379      }
380      case kAutonomous -> {
381        DriverStationJNI.observeUserProgramAutonomous();
382        autonomousPeriodic();
383        m_watchdog.addEpoch("autonomousPeriodic()");
384      }
385      case kTeleop -> {
386        DriverStationJNI.observeUserProgramTeleop();
387        teleopPeriodic();
388        m_watchdog.addEpoch("teleopPeriodic()");
389      }
390      case kTest -> {
391        DriverStationJNI.observeUserProgramTest();
392        testPeriodic();
393        m_watchdog.addEpoch("testPeriodic()");
394      }
395      default -> {
396        // NOP
397      }
398    }
399
400    robotPeriodic();
401    m_watchdog.addEpoch("robotPeriodic()");
402
403    SmartDashboard.updateValues();
404    m_watchdog.addEpoch("SmartDashboard.updateValues()");
405    LiveWindow.updateValues();
406    m_watchdog.addEpoch("LiveWindow.updateValues()");
407    Shuffleboard.update();
408    m_watchdog.addEpoch("Shuffleboard.update()");
409
410    if (isSimulation()) {
411      HAL.simPeriodicBefore();
412      simulationPeriodic();
413      HAL.simPeriodicAfter();
414      m_watchdog.addEpoch("simulationPeriodic()");
415    }
416
417    m_watchdog.disable();
418
419    // Flush NetworkTables
420    if (m_ntFlushEnabled) {
421      NetworkTableInstance.getDefault().flushLocal();
422    }
423
424    // Warn on loop time overruns
425    if (m_watchdog.isExpired()) {
426      m_watchdog.printEpochs();
427    }
428  }
429
430  /** Prints list of epochs added so far and their times. */
431  public void printWatchdogEpochs() {
432    m_watchdog.printEpochs();
433  }
434
435  private void printLoopOverrunMessage() {
436    DriverStation.reportWarning("Loop time of " + m_period + "s overrun\n", false);
437  }
438}