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 org.wpilib.framework;
006
007import org.wpilib.driverstation.DriverStationErrors;
008import org.wpilib.driverstation.internal.DriverStationBackend;
009import org.wpilib.hardware.hal.ControlWord;
010import org.wpilib.hardware.hal.DriverStationJNI;
011import org.wpilib.hardware.hal.HAL;
012import org.wpilib.hardware.hal.RobotMode;
013import org.wpilib.networktables.NetworkTableInstance;
014import org.wpilib.smartdashboard.SmartDashboard;
015import org.wpilib.system.Watchdog;
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>driverStationConnected() -- provide for initialization the first time the DS is connected
028 *
029 * <p>init() functions -- each of the following functions is called once when the appropriate mode
030 * is entered:
031 *
032 * <ul>
033 *   <li>disabledInit() -- called each and every time disabled is entered from another mode
034 *   <li>autonomousInit() -- called each and every time autonomous is entered from another mode
035 *   <li>teleopInit() -- called each and every time teleop is entered from another mode
036 *   <li>utilityInit() -- called each and every time utility is entered from another mode
037 * </ul>
038 *
039 * <p>periodic() functions -- each of these functions is called on an interval:
040 *
041 * <ul>
042 *   <li>robotPeriodic()
043 *   <li>disabledPeriodic()
044 *   <li>autonomousPeriodic()
045 *   <li>teleopPeriodic()
046 *   <li>utilityPeriodic()
047 * </ul>
048 *
049 * <p>exit() functions -- each of the following functions is called once when the appropriate mode
050 * is exited:
051 *
052 * <ul>
053 *   <li>disabledExit() -- called each and every time disabled is exited
054 *   <li>autonomousExit() -- called each and every time autonomous is exited
055 *   <li>teleopExit() -- called each and every time teleop is exited
056 *   <li>utilityExit() -- called each and every time utility is exited
057 * </ul>
058 */
059public abstract class IterativeRobotBase extends RobotBase {
060  private final ControlWord m_word = new ControlWord();
061  private RobotMode m_lastMode;
062  private final double m_period;
063  private final Watchdog m_watchdog;
064  private boolean m_calledDsConnected;
065
066  /**
067   * Constructor for IterativeRobotBase.
068   *
069   * @param period Period in seconds.
070   */
071  protected IterativeRobotBase(double period) {
072    m_period = period;
073    m_watchdog = new Watchdog(period, this::printLoopOverrunMessage);
074  }
075
076  /** Provide an alternate "main loop" via startCompetition(). */
077  @Override
078  public abstract void startCompetition();
079
080  /* ----------- Overridable initialization code ----------------- */
081
082  /**
083   * Code that needs to know the DS state should go here.
084   *
085   * <p>Users should override this method for initialization that needs to occur after the DS is
086   * connected, such as needing the alliance information.
087   */
088  public void driverStationConnected() {}
089
090  /**
091   * Robot-wide simulation initialization code should go here.
092   *
093   * <p>Users should override this method for default Robot-wide simulation related initialization
094   * which will be called when the robot is first started. It will be called exactly one time after
095   * the robot class constructor is called only when the robot is in simulation.
096   */
097  public void simulationInit() {}
098
099  /**
100   * Initialization code for disabled mode should go here.
101   *
102   * <p>Users should override this method for initialization code which will be called each time the
103   * robot enters disabled mode.
104   */
105  public void disabledInit() {}
106
107  /**
108   * Initialization code for autonomous 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 autonomous mode.
112   */
113  public void autonomousInit() {}
114
115  /**
116   * Initialization code for teleop 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 teleop mode.
120   */
121  public void teleopInit() {}
122
123  /**
124   * Initialization code for utility 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 utility mode.
128   */
129  public void utilityInit() {}
130
131  /* ----------- Overridable periodic code ----------------- */
132
133  private boolean m_rpFirstRun = true;
134
135  /** Periodic code for all robot modes should go here. */
136  public void robotPeriodic() {
137    if (m_rpFirstRun) {
138      System.out.println("Default robotPeriodic() method... Override me!");
139      m_rpFirstRun = false;
140    }
141  }
142
143  private boolean m_spFirstRun = true;
144
145  /**
146   * Periodic simulation code should go here.
147   *
148   * <p>This function is called in a simulated robot after user code executes.
149   */
150  public void simulationPeriodic() {
151    if (m_spFirstRun) {
152      System.out.println("Default simulationPeriodic() method... Override me!");
153      m_spFirstRun = false;
154    }
155  }
156
157  private boolean m_dpFirstRun = true;
158
159  /** Periodic code for disabled mode should go here. */
160  public void disabledPeriodic() {
161    if (m_dpFirstRun) {
162      System.out.println("Default disabledPeriodic() method... Override me!");
163      m_dpFirstRun = false;
164    }
165  }
166
167  private boolean m_apFirstRun = true;
168
169  /** Periodic code for autonomous mode should go here. */
170  public void autonomousPeriodic() {
171    if (m_apFirstRun) {
172      System.out.println("Default autonomousPeriodic() method... Override me!");
173      m_apFirstRun = false;
174    }
175  }
176
177  private boolean m_tpFirstRun = true;
178
179  /** Periodic code for teleop mode should go here. */
180  public void teleopPeriodic() {
181    if (m_tpFirstRun) {
182      System.out.println("Default teleopPeriodic() method... Override me!");
183      m_tpFirstRun = false;
184    }
185  }
186
187  private boolean m_tmpFirstRun = true;
188
189  /** Periodic code for utility mode should go here. */
190  public void utilityPeriodic() {
191    if (m_tmpFirstRun) {
192      System.out.println("Default utilityPeriodic() method... Override me!");
193      m_tmpFirstRun = false;
194    }
195  }
196
197  /**
198   * Exit code for disabled mode should go here.
199   *
200   * <p>Users should override this method for code which will be called each time the robot exits
201   * disabled mode.
202   */
203  public void disabledExit() {}
204
205  /**
206   * Exit code for autonomous mode should go here.
207   *
208   * <p>Users should override this method for code which will be called each time the robot exits
209   * autonomous mode.
210   */
211  public void autonomousExit() {}
212
213  /**
214   * Exit code for teleop mode should go here.
215   *
216   * <p>Users should override this method for code which will be called each time the robot exits
217   * teleop mode.
218   */
219  public void teleopExit() {}
220
221  /**
222   * Exit code for utility mode should go here.
223   *
224   * <p>Users should override this method for code which will be called each time the robot exits
225   * utility mode.
226   */
227  public void utilityExit() {}
228
229  /**
230   * Gets time period between calls to Periodic() functions.
231   *
232   * @return The time period between calls to Periodic() functions.
233   */
234  public double getPeriod() {
235    return m_period;
236  }
237
238  /** Loop function. */
239  protected final void loopFunc() {
240    DriverStationBackend.refreshData();
241    DriverStationBackend.refreshControlWordFromCache(m_word);
242    m_watchdog.reset();
243
244    // Get current mode; treat disabled as unknown
245    boolean enabled = m_word.isEnabled();
246    RobotMode mode = enabled ? m_word.getRobotMode() : RobotMode.UNKNOWN;
247
248    if (!m_calledDsConnected && m_word.isDSAttached()) {
249      m_calledDsConnected = true;
250      driverStationConnected();
251    }
252
253    // If mode changed, call mode exit and entry functions
254    if (m_lastMode != mode) {
255      if (m_lastMode != null) {
256        // Call last mode's exit function
257        switch (m_lastMode) {
258          case UNKNOWN -> disabledExit();
259          case AUTONOMOUS -> autonomousExit();
260          case TELEOPERATED -> teleopExit();
261          case UTILITY -> utilityExit();
262          default -> {
263            // NOP
264          }
265        }
266      }
267
268      // Call current mode's entry function
269      switch (mode) {
270        case UNKNOWN -> {
271          disabledInit();
272          m_watchdog.addEpoch("disabledInit()");
273        }
274        case AUTONOMOUS -> {
275          autonomousInit();
276          m_watchdog.addEpoch("autonomousInit()");
277        }
278        case TELEOPERATED -> {
279          teleopInit();
280          m_watchdog.addEpoch("teleopInit()");
281        }
282        case UTILITY -> {
283          utilityInit();
284          m_watchdog.addEpoch("utilityInit()");
285        }
286        default -> {
287          // NOP
288        }
289      }
290
291      m_lastMode = mode;
292    }
293
294    // Call the appropriate function depending upon the current robot mode
295    DriverStationJNI.observeUserProgram(m_word.getNative());
296    switch (mode) {
297      case UNKNOWN -> {
298        disabledPeriodic();
299        m_watchdog.addEpoch("disabledPeriodic()");
300      }
301      case AUTONOMOUS -> {
302        autonomousPeriodic();
303        m_watchdog.addEpoch("autonomousPeriodic()");
304      }
305      case TELEOPERATED -> {
306        teleopPeriodic();
307        m_watchdog.addEpoch("teleopPeriodic()");
308      }
309      case UTILITY -> {
310        utilityPeriodic();
311        m_watchdog.addEpoch("utilityPeriodic()");
312      }
313      default -> {
314        // NOP
315      }
316    }
317
318    robotPeriodic();
319    m_watchdog.addEpoch("robotPeriodic()");
320
321    SmartDashboard.updateValues();
322    m_watchdog.addEpoch("SmartDashboard.updateValues()");
323
324    if (isSimulation()) {
325      HAL.simPeriodicBefore();
326      simulationPeriodic();
327      HAL.simPeriodicAfter();
328      m_watchdog.addEpoch("simulationPeriodic()");
329    }
330
331    m_watchdog.disable();
332
333    // Flush NetworkTables
334    NetworkTableInstance.getDefault().flushLocal();
335
336    // Warn on loop time overruns
337    if (m_watchdog.isExpired()) {
338      m_watchdog.printEpochs();
339    }
340  }
341
342  /** Prints list of epochs added so far and their times. */
343  public void printWatchdogEpochs() {
344    m_watchdog.printEpochs();
345  }
346
347  private void printLoopOverrunMessage() {
348    DriverStationErrors.reportWarning("Loop time of " + m_period + "s overrun\n", false);
349  }
350}