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 java.io.Closeable; 010import java.util.PriorityQueue; 011import java.util.concurrent.locks.ReentrantLock; 012import org.wpilib.driverstation.DriverStation; 013import org.wpilib.hardware.hal.HALUtil; 014import org.wpilib.hardware.hal.NotifierJNI; 015import org.wpilib.units.measure.Time; 016import org.wpilib.util.WPIUtilJNI; 017 018/** 019 * A class that's a wrapper around a watchdog timer. 020 * 021 * <p>When the timer expires, a message is printed to the console and an optional user-provided 022 * callback is invoked. 023 * 024 * <p>The watchdog is initialized disabled, so the user needs to call enable() before use. 025 */ 026public class Watchdog implements Closeable, Comparable<Watchdog> { 027 // Used for timeout print rate-limiting 028 private static final long kMinPrintPeriod = (long) 1e6; // μs 029 030 private double m_startTime; 031 private double m_timeout; 032 private double m_expirationTime; 033 private final Runnable m_callback; 034 private double m_lastTimeoutPrint; // s 035 036 boolean m_isExpired; 037 038 boolean m_suppressTimeoutMessage; 039 040 private final Tracer m_tracer; 041 042 private static final PriorityQueue<Watchdog> m_watchdogs = new PriorityQueue<>(); 043 private static ReentrantLock m_queueMutex = new ReentrantLock(); 044 private static int m_notifier; 045 046 static { 047 m_notifier = NotifierJNI.createNotifier(); 048 NotifierJNI.setNotifierName(m_notifier, "Watchdog"); 049 startDaemonThread(Watchdog::schedulerFunc); 050 } 051 052 /** 053 * Watchdog constructor. 054 * 055 * @param timeout The watchdog's timeout in seconds with microsecond resolution. 056 * @param callback This function is called when the timeout expires. 057 */ 058 public Watchdog(double timeout, Runnable callback) { 059 m_timeout = timeout; 060 m_callback = callback; 061 m_tracer = new Tracer(); 062 } 063 064 /** 065 * Watchdog constructor. 066 * 067 * @param timeout The watchdog's timeout with microsecond resolution. 068 * @param callback This function is called when the timeout expires. 069 */ 070 public Watchdog(Time timeout, Runnable callback) { 071 this(timeout.in(Seconds), callback); 072 } 073 074 @Override 075 public void close() { 076 disable(); 077 } 078 079 @Override 080 public boolean equals(Object obj) { 081 return obj instanceof Watchdog watchdog 082 && Double.compare(m_expirationTime, watchdog.m_expirationTime) == 0; 083 } 084 085 @Override 086 public int hashCode() { 087 return Double.hashCode(m_expirationTime); 088 } 089 090 @Override 091 public int compareTo(Watchdog rhs) { 092 // Elements with sooner expiration times are sorted as lesser. The head of 093 // Java's PriorityQueue is the least element. 094 return Double.compare(m_expirationTime, rhs.m_expirationTime); 095 } 096 097 /** 098 * Returns the time in seconds since the watchdog was last fed. 099 * 100 * @return The time in seconds since the watchdog was last fed. 101 */ 102 public double getTime() { 103 return Timer.getFPGATimestamp() - m_startTime; 104 } 105 106 /** 107 * Sets the watchdog's timeout. 108 * 109 * @param timeout The watchdog's timeout in seconds with microsecond resolution. 110 */ 111 public void setTimeout(double timeout) { 112 m_startTime = Timer.getFPGATimestamp(); 113 m_tracer.clearEpochs(); 114 115 m_queueMutex.lock(); 116 try { 117 m_timeout = timeout; 118 m_isExpired = false; 119 120 m_watchdogs.remove(this); 121 m_expirationTime = m_startTime + m_timeout; 122 m_watchdogs.add(this); 123 updateAlarm(); 124 } finally { 125 m_queueMutex.unlock(); 126 } 127 } 128 129 /** 130 * Returns the watchdog's timeout in seconds. 131 * 132 * @return The watchdog's timeout in seconds. 133 */ 134 public double getTimeout() { 135 m_queueMutex.lock(); 136 try { 137 return m_timeout; 138 } finally { 139 m_queueMutex.unlock(); 140 } 141 } 142 143 /** 144 * Returns true if the watchdog timer has expired. 145 * 146 * @return True if the watchdog timer has expired. 147 */ 148 public boolean isExpired() { 149 m_queueMutex.lock(); 150 try { 151 return m_isExpired; 152 } finally { 153 m_queueMutex.unlock(); 154 } 155 } 156 157 /** 158 * Adds time since last epoch to the list printed by printEpochs(). 159 * 160 * @see Tracer#addEpoch(String) 161 * @param epochName The name to associate with the epoch. 162 */ 163 public void addEpoch(String epochName) { 164 m_tracer.addEpoch(epochName); 165 } 166 167 /** 168 * Prints list of epochs added so far and their times. 169 * 170 * @see Tracer#printEpochs() 171 */ 172 public void printEpochs() { 173 m_tracer.printEpochs(); 174 } 175 176 /** 177 * Resets the watchdog timer. 178 * 179 * <p>This also enables the timer if it was previously disabled. 180 */ 181 public void reset() { 182 enable(); 183 } 184 185 /** Enables the watchdog timer. */ 186 public void enable() { 187 m_startTime = Timer.getFPGATimestamp(); 188 m_tracer.clearEpochs(); 189 190 m_queueMutex.lock(); 191 try { 192 m_isExpired = false; 193 194 m_watchdogs.remove(this); 195 m_expirationTime = m_startTime + m_timeout; 196 m_watchdogs.add(this); 197 updateAlarm(); 198 } finally { 199 m_queueMutex.unlock(); 200 } 201 } 202 203 /** Disables the watchdog timer. */ 204 public void disable() { 205 m_queueMutex.lock(); 206 try { 207 m_watchdogs.remove(this); 208 updateAlarm(); 209 } finally { 210 m_queueMutex.unlock(); 211 } 212 } 213 214 /** 215 * Enable or disable suppression of the generic timeout message. 216 * 217 * <p>This may be desirable if the user-provided callback already prints a more specific message. 218 * 219 * @param suppress Whether to suppress generic timeout message. 220 */ 221 public void suppressTimeoutMessage(boolean suppress) { 222 m_suppressTimeoutMessage = suppress; 223 } 224 225 @SuppressWarnings("resource") 226 private static void updateAlarm() { 227 if (m_watchdogs.isEmpty()) { 228 NotifierJNI.cancelNotifierAlarm(m_notifier, true); 229 } else { 230 NotifierJNI.setNotifierAlarm( 231 m_notifier, (long) (m_watchdogs.peek().m_expirationTime * 1e6), 0, true, true); 232 } 233 } 234 235 private static Thread startDaemonThread(Runnable target) { 236 Thread inst = new Thread(target); 237 inst.setDaemon(true); 238 inst.start(); 239 return inst; 240 } 241 242 private static void schedulerFunc() { 243 while (!Thread.currentThread().isInterrupted()) { 244 try { 245 WPIUtilJNI.waitForObject(m_notifier); 246 } catch (InterruptedException ex) { 247 Thread.currentThread().interrupt(); 248 break; 249 } 250 long curTime = HALUtil.getFPGATime(); 251 252 m_queueMutex.lock(); 253 try { 254 if (m_watchdogs.isEmpty()) { 255 continue; 256 } 257 258 // If the condition variable timed out, that means a Watchdog timeout 259 // has occurred, so call its timeout function. 260 Watchdog watchdog = m_watchdogs.poll(); 261 262 double now = curTime * 1e-6; 263 if (now - watchdog.m_lastTimeoutPrint > kMinPrintPeriod) { 264 watchdog.m_lastTimeoutPrint = now; 265 if (!watchdog.m_suppressTimeoutMessage) { 266 DriverStation.reportWarning( 267 String.format("Watchdog not fed within %.6fs\n", watchdog.m_timeout), false); 268 } 269 } 270 271 // Set expiration flag before calling the callback so any 272 // manipulation of the flag in the callback (e.g., calling 273 // Disable()) isn't clobbered. 274 watchdog.m_isExpired = true; 275 276 m_queueMutex.unlock(); 277 watchdog.m_callback.run(); 278 m_queueMutex.lock(); 279 280 updateAlarm(); 281 } finally { 282 m_queueMutex.unlock(); 283 } 284 } 285 } 286}