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.hal.NotifierJNI; 012import java.util.PriorityQueue; 013 014/** 015 * TimedRobot implements the IterativeRobotBase robot program framework. 016 * 017 * <p>The TimedRobot class is intended to be subclassed by a user creating a robot program. 018 * 019 * <p>periodic() functions from the base class are called on an interval by a Notifier instance. 020 */ 021public class TimedRobot extends IterativeRobotBase { 022 @SuppressWarnings("MemberName") 023 static class Callback implements Comparable<Callback> { 024 public Runnable func; 025 public double period; 026 public double expirationTime; 027 028 /** 029 * Construct a callback container. 030 * 031 * @param func The callback to run. 032 * @param startTimeSeconds The common starting point for all callback scheduling in seconds. 033 * @param periodSeconds The period at which to run the callback in seconds. 034 * @param offsetSeconds The offset from the common starting time in seconds. 035 */ 036 Callback(Runnable func, double startTimeSeconds, double periodSeconds, double offsetSeconds) { 037 this.func = func; 038 this.period = periodSeconds; 039 this.expirationTime = 040 startTimeSeconds 041 + offsetSeconds 042 + Math.floor((Timer.getFPGATimestamp() - startTimeSeconds) / this.period) 043 * this.period 044 + this.period; 045 } 046 047 @Override 048 public boolean equals(Object rhs) { 049 if (rhs instanceof Callback) { 050 return Double.compare(expirationTime, ((Callback) rhs).expirationTime) == 0; 051 } 052 return false; 053 } 054 055 @Override 056 public int hashCode() { 057 return Double.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 Double.compare(expirationTime, rhs.expirationTime); 065 } 066 } 067 068 public static final double kDefaultPeriod = 0.02; 069 070 // The C pointer to the notifier object. We don't use it directly, it is 071 // just passed to the JNI bindings. 072 private final int m_notifier = NotifierJNI.initializeNotifier(); 073 074 private double m_startTime; 075 076 private final PriorityQueue<Callback> m_callbacks = new PriorityQueue<>(); 077 078 /** Constructor for TimedRobot. */ 079 protected TimedRobot() { 080 this(kDefaultPeriod); 081 } 082 083 /** 084 * Constructor for TimedRobot. 085 * 086 * @param period Period in seconds. 087 */ 088 protected TimedRobot(double period) { 089 super(period); 090 m_startTime = Timer.getFPGATimestamp(); 091 addPeriodic(this::loopFunc, period); 092 NotifierJNI.setNotifierName(m_notifier, "TimedRobot"); 093 094 HAL.report(tResourceType.kResourceType_Framework, tInstances.kFramework_Timed); 095 } 096 097 @Override 098 public void close() { 099 NotifierJNI.stopNotifier(m_notifier); 100 NotifierJNI.cleanNotifier(m_notifier); 101 } 102 103 /** Provide an alternate "main loop" via startCompetition(). */ 104 @Override 105 public void startCompetition() { 106 robotInit(); 107 108 if (isSimulation()) { 109 simulationInit(); 110 } 111 112 // Tell the DS that the robot is ready to be enabled 113 System.out.println("********** Robot program startup complete **********"); 114 DriverStationJNI.observeUserProgramStarting(); 115 116 // Loop forever, calling the appropriate mode-dependent function 117 while (true) { 118 // We don't have to check there's an element in the queue first because 119 // there's always at least one (the constructor adds one). It's reenqueued 120 // at the end of the loop. 121 var callback = m_callbacks.poll(); 122 123 NotifierJNI.updateNotifierAlarm(m_notifier, (long) (callback.expirationTime * 1e6)); 124 125 long curTime = NotifierJNI.waitForNotifierAlarm(m_notifier); 126 if (curTime == 0) { 127 break; 128 } 129 130 callback.func.run(); 131 132 callback.expirationTime += callback.period; 133 m_callbacks.add(callback); 134 135 // Process all other callbacks that are ready to run 136 while ((long) (m_callbacks.peek().expirationTime * 1e6) <= curTime) { 137 callback = m_callbacks.poll(); 138 139 callback.func.run(); 140 141 callback.expirationTime += callback.period; 142 m_callbacks.add(callback); 143 } 144 } 145 } 146 147 /** Ends the main loop in startCompetition(). */ 148 @Override 149 public void endCompetition() { 150 NotifierJNI.stopNotifier(m_notifier); 151 } 152 153 /** 154 * Add a callback to run at a specific period. 155 * 156 * <p>This is scheduled on TimedRobot's Notifier, so TimedRobot and the callback run 157 * synchronously. Interactions between them are thread-safe. 158 * 159 * @param callback The callback to run. 160 * @param periodSeconds The period at which to run the callback in seconds. 161 */ 162 public void addPeriodic(Runnable callback, double periodSeconds) { 163 m_callbacks.add(new Callback(callback, m_startTime, periodSeconds, 0.0)); 164 } 165 166 /** 167 * Add a callback to run at a specific period with a starting time offset. 168 * 169 * <p>This is scheduled on TimedRobot's Notifier, so TimedRobot and the callback run 170 * synchronously. Interactions between them are thread-safe. 171 * 172 * @param callback The callback to run. 173 * @param periodSeconds The period at which to run the callback in seconds. 174 * @param offsetSeconds The offset from the common starting time in seconds. This is useful for 175 * scheduling a callback in a different timeslot relative to TimedRobot. 176 */ 177 public void addPeriodic(Runnable callback, double periodSeconds, double offsetSeconds) { 178 m_callbacks.add(new Callback(callback, m_startTime, periodSeconds, offsetSeconds)); 179 } 180}