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 static edu.wpi.first.units.Units.Seconds; 008 009import edu.wpi.first.hal.DriverStationJNI; 010import edu.wpi.first.hal.FRCNetComm.tInstances; 011import edu.wpi.first.hal.FRCNetComm.tResourceType; 012import edu.wpi.first.hal.HAL; 013import edu.wpi.first.hal.NotifierJNI; 014import edu.wpi.first.units.measure.Frequency; 015import edu.wpi.first.units.measure.Time; 016import java.util.PriorityQueue; 017 018/** 019 * TimedRobot implements the IterativeRobotBase robot program framework. 020 * 021 * <p>The TimedRobot class is intended to be subclassed by a user creating a robot program. 022 * 023 * <p>periodic() functions from the base class are called on an interval by a Notifier instance. 024 */ 025public class TimedRobot extends IterativeRobotBase { 026 @SuppressWarnings("MemberName") 027 static class Callback implements Comparable<Callback> { 028 public Runnable func; 029 public long period; 030 public long expirationTime; 031 032 /** 033 * Construct a callback container. 034 * 035 * @param func The callback to run. 036 * @param startTimeUs The common starting point for all callback scheduling in microseconds. 037 * @param periodUs The period at which to run the callback in microseconds. 038 * @param offsetUs The offset from the common starting time in microseconds. 039 */ 040 Callback(Runnable func, long startTimeUs, long periodUs, long offsetUs) { 041 this.func = func; 042 this.period = periodUs; 043 this.expirationTime = 044 startTimeUs 045 + offsetUs 046 + this.period 047 + (RobotController.getFPGATime() - startTimeUs) / this.period * this.period; 048 } 049 050 @Override 051 public boolean equals(Object rhs) { 052 return rhs instanceof Callback callback && expirationTime == callback.expirationTime; 053 } 054 055 @Override 056 public int hashCode() { 057 return Long.hashCode(expirationTime); 058 } 059 060 @Override 061 public int compareTo(Callback rhs) { 062 // Elements with sooner expiration times are sorted as lesser. The head of 063 // Java's PriorityQueue is the least element. 064 return Long.compare(expirationTime, rhs.expirationTime); 065 } 066 } 067 068 /** Default loop period. */ 069 public static final double kDefaultPeriod = 0.02; 070 071 // The C pointer to the notifier object. We don't use it directly, it is 072 // just passed to the JNI bindings. 073 private final int m_notifier = NotifierJNI.initializeNotifier(); 074 075 private long m_startTimeUs; 076 private long m_loopStartTimeUs; 077 078 private final PriorityQueue<Callback> m_callbacks = new PriorityQueue<>(); 079 080 /** Constructor for TimedRobot. */ 081 protected TimedRobot() { 082 this(kDefaultPeriod); 083 } 084 085 /** 086 * Constructor for TimedRobot. 087 * 088 * @param period The period of the robot loop function. 089 */ 090 protected TimedRobot(double period) { 091 super(period); 092 m_startTimeUs = RobotController.getFPGATime(); 093 addPeriodic(this::loopFunc, period); 094 NotifierJNI.setNotifierName(m_notifier, "TimedRobot"); 095 096 HAL.report(tResourceType.kResourceType_Framework, tInstances.kFramework_Timed); 097 } 098 099 /** 100 * Constructor for TimedRobot. 101 * 102 * @param period The period of the robot loop function. 103 */ 104 protected TimedRobot(Time period) { 105 this(period.in(Seconds)); 106 } 107 108 /** 109 * Constructor for TimedRobot. 110 * 111 * @param frequency The frequency of the robot loop function. 112 */ 113 protected TimedRobot(Frequency frequency) { 114 this(frequency.asPeriod()); 115 } 116 117 @Override 118 public void close() { 119 NotifierJNI.stopNotifier(m_notifier); 120 NotifierJNI.cleanNotifier(m_notifier); 121 } 122 123 /** Provide an alternate "main loop" via startCompetition(). */ 124 @Override 125 public void startCompetition() { 126 robotInit(); 127 128 if (isSimulation()) { 129 simulationInit(); 130 } 131 132 // Tell the DS that the robot is ready to be enabled 133 System.out.println("********** Robot program startup complete **********"); 134 DriverStationJNI.observeUserProgramStarting(); 135 136 // Loop forever, calling the appropriate mode-dependent function 137 while (true) { 138 // We don't have to check there's an element in the queue first because 139 // there's always at least one (the constructor adds one). It's reenqueued 140 // at the end of the loop. 141 var callback = m_callbacks.poll(); 142 143 NotifierJNI.updateNotifierAlarm(m_notifier, callback.expirationTime); 144 145 long currentTime = NotifierJNI.waitForNotifierAlarm(m_notifier); 146 if (currentTime == 0) { 147 break; 148 } 149 150 m_loopStartTimeUs = RobotController.getFPGATime(); 151 152 callback.func.run(); 153 154 // Increment the expiration time by the number of full periods it's behind 155 // plus one to avoid rapid repeat fires from a large loop overrun. We 156 // assume currentTime ≥ expirationTime rather than checking for it since 157 // the callback wouldn't be running otherwise. 158 callback.expirationTime += 159 callback.period 160 + (currentTime - callback.expirationTime) / callback.period * callback.period; 161 m_callbacks.add(callback); 162 163 // Process all other callbacks that are ready to run 164 while (m_callbacks.peek().expirationTime <= currentTime) { 165 callback = m_callbacks.poll(); 166 167 callback.func.run(); 168 169 callback.expirationTime += 170 callback.period 171 + (currentTime - callback.expirationTime) / callback.period * callback.period; 172 m_callbacks.add(callback); 173 } 174 } 175 } 176 177 /** Ends the main loop in startCompetition(). */ 178 @Override 179 public void endCompetition() { 180 NotifierJNI.stopNotifier(m_notifier); 181 } 182 183 /** 184 * Return the system clock time in micrseconds for the start of the current periodic loop. This is 185 * in the same time base as Timer.getFPGATimestamp(), but is stable through a loop. It is updated 186 * at the beginning of every periodic callback (including the normal periodic loop). 187 * 188 * @return Robot running time in microseconds, as of the start of the current periodic function. 189 */ 190 public long getLoopStartTime() { 191 return m_loopStartTimeUs; 192 } 193 194 /** 195 * Add a callback to run at a specific period. 196 * 197 * <p>This is scheduled on TimedRobot's Notifier, so TimedRobot and the callback run 198 * synchronously. Interactions between them are thread-safe. 199 * 200 * @param callback The callback to run. 201 * @param periodSeconds The period at which to run the callback in seconds. 202 */ 203 public final void addPeriodic(Runnable callback, double periodSeconds) { 204 m_callbacks.add(new Callback(callback, m_startTimeUs, (long) (periodSeconds * 1e6), 0)); 205 } 206 207 /** 208 * Add a callback to run at a specific period with a starting time offset. 209 * 210 * <p>This is scheduled on TimedRobot's Notifier, so TimedRobot and the callback run 211 * synchronously. Interactions between them are thread-safe. 212 * 213 * @param callback The callback to run. 214 * @param periodSeconds The period at which to run the callback in seconds. 215 * @param offsetSeconds The offset from the common starting time in seconds. This is useful for 216 * scheduling a callback in a different timeslot relative to TimedRobot. 217 */ 218 public final void addPeriodic(Runnable callback, double periodSeconds, double offsetSeconds) { 219 m_callbacks.add( 220 new Callback( 221 callback, m_startTimeUs, (long) (periodSeconds * 1e6), (long) (offsetSeconds * 1e6))); 222 } 223 224 /** 225 * Add a callback to run at a specific period. 226 * 227 * <p>This is scheduled on TimedRobot's Notifier, so TimedRobot and the callback run 228 * synchronously. Interactions between them are thread-safe. 229 * 230 * @param callback The callback to run. 231 * @param period The period at which to run the callback. 232 */ 233 public final void addPeriodic(Runnable callback, Time period) { 234 addPeriodic(callback, period.in(Seconds)); 235 } 236 237 /** 238 * Add a callback to run at a specific period with a starting time offset. 239 * 240 * <p>This is scheduled on TimedRobot's Notifier, so TimedRobot and the callback run 241 * synchronously. Interactions between them are thread-safe. 242 * 243 * @param callback The callback to run. 244 * @param period The period at which to run the callback. 245 * @param offset The offset from the common starting time. This is useful for scheduling a 246 * callback in a different timeslot relative to TimedRobot. 247 */ 248 public final void addPeriodic(Runnable callback, Time period, Time offset) { 249 addPeriodic(callback, period.in(Seconds), offset.in(Seconds)); 250 } 251}