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.system;
006
007import static org.wpilib.units.Units.Seconds;
008
009import org.wpilib.driverstation.DriverStation;
010import org.wpilib.units.measure.Time;
011
012/**
013 * A timer class.
014 *
015 * <p>Note that if the user calls SimHooks.restartTiming(), they should also reset the timer so
016 * get() won't return a negative duration.
017 */
018public class Timer {
019  /**
020   * Return the clock time in seconds. By default, the time is based on the FPGA hardware clock in
021   * seconds since the FPGA started. However, the return value of this method may be modified to use
022   * any time base, including non-monotonic time bases.
023   *
024   * @return Robot running time in seconds.
025   */
026  public static double getTimestamp() {
027    return RobotController.getTime() / 1000000.0;
028  }
029
030  /**
031   * Return the system clock time in seconds. Return the time from the FPGA hardware clock in
032   * seconds since the FPGA started.
033   *
034   * @return Robot running time in seconds.
035   */
036  public static double getFPGATimestamp() {
037    return RobotController.getFPGATime() / 1000000.0;
038  }
039
040  /**
041   * Return the approximate match time. The FMS does not send an official match time to the robots,
042   * but does send an approximate match time. The value will count down the time remaining in the
043   * current period (auto or teleop). Warning: This is not an official time (so it cannot be used to
044   * dispute ref calls or guarantee that a function will trigger before the match ends).
045   *
046   * <p>When connected to the real field, this number only changes in full integer increments, and
047   * always counts down.
048   *
049   * <p>When the DS is in practice mode, this number is a floating point number, and counts down.
050   *
051   * <p>When the DS is in teleop or autonomous mode, this number returns -1.0.
052   *
053   * <p>Simulation matches DS behavior without an FMS connected.
054   *
055   * @return Time remaining in current match period (auto or teleop) in seconds
056   */
057  public static double getMatchTime() {
058    return DriverStation.getMatchTime();
059  }
060
061  /**
062   * Pause the execution of the thread for a specified period of time. Motors will continue to run
063   * at their last assigned values, and sensors will continue to update. Only the task containing
064   * the wait will pause until the wait time is expired.
065   *
066   * @param period Length of time to pause
067   */
068  public static void delay(final Time period) {
069    delay(period.in(Seconds));
070  }
071
072  /**
073   * Pause the execution of the thread for a specified period of time given in seconds. Motors will
074   * continue to run at their last assigned values, and sensors will continue to update. Only the
075   * task containing the wait will pause until the wait time is expired.
076   *
077   * @param seconds Length of time to pause
078   */
079  public static void delay(final double seconds) {
080    try {
081      Thread.sleep((long) (seconds * 1e3));
082    } catch (final InterruptedException ex) {
083      Thread.currentThread().interrupt();
084    }
085  }
086
087  private double m_startTime;
088  private double m_accumulatedTime;
089  private boolean m_running;
090
091  /** Timer constructor. */
092  public Timer() {
093    reset();
094  }
095
096  private double getMsClock() {
097    return RobotController.getTime() / 1000.0;
098  }
099
100  /**
101   * Get the current time from the timer. If the clock is running it is derived from the current
102   * system clock the start time stored in the timer class. If the clock is not running, then return
103   * the time when it was last stopped.
104   *
105   * @return Current time value for this timer in seconds
106   */
107  public double get() {
108    if (m_running) {
109      return m_accumulatedTime + (getMsClock() - m_startTime) / 1000.0;
110    } else {
111      return m_accumulatedTime;
112    }
113  }
114
115  /**
116   * Reset the timer by setting the time to 0.
117   *
118   * <p>Make the timer startTime the current time so new requests will be relative now.
119   */
120  public final void reset() {
121    m_accumulatedTime = 0;
122    m_startTime = getMsClock();
123  }
124
125  /**
126   * Start the timer running. Just set the running flag to true indicating that all time requests
127   * should be relative to the system clock. Note that this method is a no-op if the timer is
128   * already running.
129   */
130  public void start() {
131    if (!m_running) {
132      m_startTime = getMsClock();
133      m_running = true;
134    }
135  }
136
137  /**
138   * Restart the timer by stopping the timer, if it is not already stopped, resetting the
139   * accumulated time, then starting the timer again. If you want an event to periodically reoccur
140   * at some time interval from the start time, consider using advanceIfElapsed() instead.
141   */
142  public void restart() {
143    if (m_running) {
144      stop();
145    }
146    reset();
147    start();
148  }
149
150  /**
151   * Stop the timer. This computes the time as of now and clears the running flag, causing all
152   * subsequent time requests to be read from the accumulated time rather than looking at the system
153   * clock.
154   */
155  public void stop() {
156    m_accumulatedTime = get();
157    m_running = false;
158  }
159
160  /**
161   * Check if the period specified has passed.
162   *
163   * @param period The period to check.
164   * @return Whether the period has passed.
165   */
166  public boolean hasElapsed(Time period) {
167    return hasElapsed(period.in(Seconds));
168  }
169
170  /**
171   * Check if the period specified has passed.
172   *
173   * @param seconds The period to check in seconds.
174   * @return Whether the period has passed.
175   */
176  public boolean hasElapsed(double seconds) {
177    return get() >= seconds;
178  }
179
180  /**
181   * Check if the period specified has passed and if it has, advance the start time by that period.
182   * This is useful to decide if it's time to do periodic work without drifting later by the time it
183   * took to get around to checking.
184   *
185   * @param seconds The period to check.
186   * @return Whether the period has passed.
187   */
188  public boolean advanceIfElapsed(double seconds) {
189    if (get() >= seconds) {
190      // Advance the start time by the period.
191      // Don't set it to the current time... we want to avoid drift.
192      m_startTime += seconds * 1000;
193      return true;
194    } else {
195      return false;
196    }
197  }
198
199  /**
200   * Whether the timer is currently running.
201   *
202   * @return true if running.
203   */
204  public boolean isRunning() {
205    return m_running;
206  }
207}