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.HAL;
008import edu.wpi.first.networktables.NetworkTableInstance;
009import edu.wpi.first.wpilibj.livewindow.LiveWindow;
010import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard;
011import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
012
013/**
014 * IterativeRobotBase implements a specific type of robot program framework, extending the RobotBase
015 * class.
016 *
017 * <p>The IterativeRobotBase class does not implement startCompetition(), so it should not be used
018 * by teams directly.
019 *
020 * <p>This class provides the following functions which are called by the main loop,
021 * startCompetition(), at the appropriate times:
022 *
023 * <p>robotInit() -- provide for initialization at robot power-on
024 *
025 * <p>init() functions -- each of the following functions is called once when the appropriate mode
026 * is entered:
027 *
028 * <ul>
029 *   <li>disabledInit() -- called each and every time disabled is entered from another mode
030 *   <li>autonomousInit() -- called each and every time autonomous is entered from another mode
031 *   <li>teleopInit() -- called each and every time teleop is entered from another mode
032 *   <li>testInit() -- called each and every time test is entered from another mode
033 * </ul>
034 *
035 * <p>periodic() functions -- each of these functions is called on an interval:
036 *
037 * <ul>
038 *   <li>robotPeriodic()
039 *   <li>disabledPeriodic()
040 *   <li>autonomousPeriodic()
041 *   <li>teleopPeriodic()
042 *   <li>testPeriodic()
043 * </ul>
044 *
045 * <p>final() functions -- each of the following functions is called once when the appropriate mode
046 * is exited:
047 *
048 * <ul>
049 *   <li>disabledExit() -- called each and every time disabled is exited
050 *   <li>autonomousExit() -- called each and every time autonomous is exited
051 *   <li>teleopExit() -- called each and every time teleop is exited
052 *   <li>testExit() -- called each and every time test is exited
053 * </ul>
054 */
055public abstract class IterativeRobotBase extends RobotBase {
056  private enum Mode {
057    kNone,
058    kDisabled,
059    kAutonomous,
060    kTeleop,
061    kTest
062  }
063
064  private final DSControlWord m_word = new DSControlWord();
065  private Mode m_lastMode = Mode.kNone;
066  private final double m_period;
067  private final Watchdog m_watchdog;
068  private boolean m_ntFlushEnabled;
069
070  /**
071   * Constructor for IterativeRobotBase.
072   *
073   * @param period Period in seconds.
074   */
075  protected IterativeRobotBase(double period) {
076    m_period = period;
077    m_watchdog = new Watchdog(period, this::printLoopOverrunMessage);
078  }
079
080  /** Provide an alternate "main loop" via startCompetition(). */
081  @Override
082  public abstract void startCompetition();
083
084  /* ----------- Overridable initialization code ----------------- */
085
086  /**
087   * Robot-wide initialization code should go here.
088   *
089   * <p>Users should override this method for default Robot-wide initialization which will be called
090   * when the robot is first powered on. It will be called exactly one time.
091   *
092   * <p>Warning: the Driver Station "Robot Code" light and FMS "Robot Ready" indicators will be off
093   * until RobotInit() exits. Code in RobotInit() that waits for enable will cause the robot to
094   * never indicate that the code is ready, causing the robot to be bypassed in a match.
095   */
096  public void robotInit() {}
097
098  /**
099   * Robot-wide simulation initialization code should go here.
100   *
101   * <p>Users should override this method for default Robot-wide simulation related initialization
102   * which will be called when the robot is first started. It will be called exactly one time after
103   * RobotInit is called only when the robot is in simulation.
104   */
105  public void simulationInit() {}
106
107  /**
108   * Initialization code for disabled mode should go here.
109   *
110   * <p>Users should override this method for initialization code which will be called each time the
111   * robot enters disabled mode.
112   */
113  public void disabledInit() {}
114
115  /**
116   * Initialization code for autonomous mode should go here.
117   *
118   * <p>Users should override this method for initialization code which will be called each time the
119   * robot enters autonomous mode.
120   */
121  public void autonomousInit() {}
122
123  /**
124   * Initialization code for teleop mode should go here.
125   *
126   * <p>Users should override this method for initialization code which will be called each time the
127   * robot enters teleop mode.
128   */
129  public void teleopInit() {}
130
131  /**
132   * Initialization code for test mode should go here.
133   *
134   * <p>Users should override this method for initialization code which will be called each time the
135   * robot enters test mode.
136   */
137  @SuppressWarnings("PMD.JUnit4TestShouldUseTestAnnotation")
138  public void testInit() {}
139
140  /* ----------- Overridable periodic code ----------------- */
141
142  private boolean m_rpFirstRun = true;
143
144  /** Periodic code for all robot modes should go here. */
145  public void robotPeriodic() {
146    if (m_rpFirstRun) {
147      System.out.println("Default robotPeriodic() method... Override me!");
148      m_rpFirstRun = false;
149    }
150  }
151
152  private boolean m_spFirstRun = true;
153
154  /**
155   * Periodic simulation code should go here.
156   *
157   * <p>This function is called in a simulated robot after user code executes.
158   */
159  public void simulationPeriodic() {
160    if (m_spFirstRun) {
161      System.out.println("Default simulationPeriodic() method... Override me!");
162      m_spFirstRun = false;
163    }
164  }
165
166  private boolean m_dpFirstRun = true;
167
168  /** Periodic code for disabled mode should go here. */
169  public void disabledPeriodic() {
170    if (m_dpFirstRun) {
171      System.out.println("Default disabledPeriodic() method... Override me!");
172      m_dpFirstRun = false;
173    }
174  }
175
176  private boolean m_apFirstRun = true;
177
178  /** Periodic code for autonomous mode should go here. */
179  public void autonomousPeriodic() {
180    if (m_apFirstRun) {
181      System.out.println("Default autonomousPeriodic() method... Override me!");
182      m_apFirstRun = false;
183    }
184  }
185
186  private boolean m_tpFirstRun = true;
187
188  /** Periodic code for teleop mode should go here. */
189  public void teleopPeriodic() {
190    if (m_tpFirstRun) {
191      System.out.println("Default teleopPeriodic() method... Override me!");
192      m_tpFirstRun = false;
193    }
194  }
195
196  private boolean m_tmpFirstRun = true;
197
198  /** Periodic code for test mode should go here. */
199  @SuppressWarnings("PMD.JUnit4TestShouldUseTestAnnotation")
200  public void testPeriodic() {
201    if (m_tmpFirstRun) {
202      System.out.println("Default testPeriodic() method... Override me!");
203      m_tmpFirstRun = false;
204    }
205  }
206
207  /**
208   * Exit code for disabled mode should go here.
209   *
210   * <p>Users should override this method for code which will be called each time the robot exits
211   * disabled mode.
212   */
213  public void disabledExit() {}
214
215  /**
216   * Exit code for autonomous mode should go here.
217   *
218   * <p>Users should override this method for code which will be called each time the robot exits
219   * autonomous mode.
220   */
221  public void autonomousExit() {}
222
223  /**
224   * Exit code for teleop mode should go here.
225   *
226   * <p>Users should override this method for code which will be called each time the robot exits
227   * teleop mode.
228   */
229  public void teleopExit() {}
230
231  /**
232   * Exit code for test mode should go here.
233   *
234   * <p>Users should override this method for code which will be called each time the robot exits
235   * test mode.
236   */
237  @SuppressWarnings("PMD.JUnit4TestShouldUseTestAnnotation")
238  public void testExit() {}
239
240  /**
241   * Enables or disables flushing NetworkTables every loop iteration. By default, this is disabled.
242   *
243   * @param enabled True to enable, false to disable
244   */
245  public void setNetworkTablesFlushEnabled(boolean enabled) {
246    m_ntFlushEnabled = enabled;
247  }
248
249  /**
250   * Gets time period between calls to Periodic() functions.
251   *
252   * @return The time period between calls to Periodic() functions.
253   */
254  public double getPeriod() {
255    return m_period;
256  }
257
258  protected void loopFunc() {
259    m_watchdog.reset();
260
261    // Get current mode
262    m_word.update();
263    Mode mode = Mode.kNone;
264    if (m_word.isDisabled()) {
265      mode = Mode.kDisabled;
266    } else if (m_word.isAutonomous()) {
267      mode = Mode.kAutonomous;
268    } else if (m_word.isTeleop()) {
269      mode = Mode.kTeleop;
270    } else if (m_word.isTest()) {
271      mode = Mode.kTest;
272    }
273
274    // If mode changed, call mode exit and entry functions
275    if (m_lastMode != mode) {
276      // Call last mode's exit function
277      if (m_lastMode == Mode.kDisabled) {
278        disabledExit();
279      } else if (m_lastMode == Mode.kAutonomous) {
280        autonomousExit();
281      } else if (m_lastMode == Mode.kTeleop) {
282        teleopExit();
283      } else if (m_lastMode == Mode.kTest) {
284        LiveWindow.setEnabled(false);
285        Shuffleboard.disableActuatorWidgets();
286        testExit();
287      }
288
289      // Call current mode's entry function
290      if (mode == Mode.kDisabled) {
291        disabledInit();
292        m_watchdog.addEpoch("disabledInit()");
293      } else if (mode == Mode.kAutonomous) {
294        autonomousInit();
295        m_watchdog.addEpoch("autonomousInit()");
296      } else if (mode == Mode.kTeleop) {
297        teleopInit();
298        m_watchdog.addEpoch("teleopInit()");
299      } else if (mode == Mode.kTest) {
300        LiveWindow.setEnabled(true);
301        Shuffleboard.enableActuatorWidgets();
302        testInit();
303        m_watchdog.addEpoch("testInit()");
304      }
305
306      m_lastMode = mode;
307    }
308
309    // Call the appropriate function depending upon the current robot mode
310    if (mode == Mode.kDisabled) {
311      HAL.observeUserProgramDisabled();
312      disabledPeriodic();
313      m_watchdog.addEpoch("disabledPeriodic()");
314    } else if (mode == Mode.kAutonomous) {
315      HAL.observeUserProgramAutonomous();
316      autonomousPeriodic();
317      m_watchdog.addEpoch("autonomousPeriodic()");
318    } else if (mode == Mode.kTeleop) {
319      HAL.observeUserProgramTeleop();
320      teleopPeriodic();
321      m_watchdog.addEpoch("teleopPeriodic()");
322    } else {
323      HAL.observeUserProgramTest();
324      testPeriodic();
325      m_watchdog.addEpoch("testPeriodic()");
326    }
327
328    robotPeriodic();
329    m_watchdog.addEpoch("robotPeriodic()");
330
331    SmartDashboard.updateValues();
332    m_watchdog.addEpoch("SmartDashboard.updateValues()");
333    LiveWindow.updateValues();
334    m_watchdog.addEpoch("LiveWindow.updateValues()");
335    Shuffleboard.update();
336    m_watchdog.addEpoch("Shuffleboard.update()");
337
338    if (isSimulation()) {
339      HAL.simPeriodicBefore();
340      simulationPeriodic();
341      HAL.simPeriodicAfter();
342      m_watchdog.addEpoch("simulationPeriodic()");
343    }
344
345    m_watchdog.disable();
346
347    // Flush NetworkTables
348    if (m_ntFlushEnabled) {
349      NetworkTableInstance.getDefault().flush();
350    }
351
352    // Warn on loop time overruns
353    if (m_watchdog.isExpired()) {
354      m_watchdog.printEpochs();
355    }
356  }
357
358  private void printLoopOverrunMessage() {
359    DriverStation.reportWarning("Loop time of " + m_period + "s overrun\n", false);
360  }
361}