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.util.ErrorMessages.requireNonNullParam; 008 009import edu.wpi.first.hal.NotifierJNI; 010import java.util.concurrent.atomic.AtomicInteger; 011import java.util.concurrent.locks.ReentrantLock; 012 013/** 014 * Notifiers run a callback function on a separate thread at a specified period. 015 * 016 * <p>If startSingle() is used, the callback will run once. If startPeriodic() is used, the callback 017 * will run repeatedly with the given period until stop() is called. 018 */ 019public class Notifier implements AutoCloseable { 020 // The thread waiting on the HAL alarm. 021 private Thread m_thread; 022 // The lock for the process information. 023 private final ReentrantLock m_processLock = new ReentrantLock(); 024 // The C pointer to the notifier object. We don't use it directly, it is 025 // just passed to the JNI bindings. 026 private final AtomicInteger m_notifier = new AtomicInteger(); 027 // The time, in seconds, at which the corresponding handler should be 028 // called. Has the same zero as RobotController.getFPGATime(). 029 private double m_expirationTimeSeconds; 030 // The handler passed in by the user which should be called at the 031 // appropriate interval. 032 private Runnable m_handler; 033 // Whether we are calling the handler just once or periodically. 034 private boolean m_periodic; 035 // If periodic, the period of the calling; if just once, stores how long it 036 // is until we call the handler. 037 private double m_periodSeconds; 038 039 @Override 040 public void close() { 041 int handle = m_notifier.getAndSet(0); 042 if (handle == 0) { 043 return; 044 } 045 NotifierJNI.stopNotifier(handle); 046 // Join the thread to ensure the handler has exited. 047 if (m_thread.isAlive()) { 048 try { 049 m_thread.interrupt(); 050 m_thread.join(); 051 } catch (InterruptedException ex) { 052 Thread.currentThread().interrupt(); 053 } 054 } 055 NotifierJNI.cleanNotifier(handle); 056 m_thread = null; 057 } 058 059 /** 060 * Update the alarm hardware to reflect the next alarm. 061 * 062 * @param triggerTimeMicroS the time in microseconds at which the next alarm will be triggered 063 */ 064 private void updateAlarm(long triggerTimeMicroS) { 065 int notifier = m_notifier.get(); 066 if (notifier == 0) { 067 return; 068 } 069 NotifierJNI.updateNotifierAlarm(notifier, triggerTimeMicroS); 070 } 071 072 /** Update the alarm hardware to reflect the next alarm. */ 073 private void updateAlarm() { 074 updateAlarm((long) (m_expirationTimeSeconds * 1e6)); 075 } 076 077 /** 078 * Create a Notifier for timer event notification. 079 * 080 * @param run The handler that is called at the notification time which is set using StartSingle 081 * or StartPeriodic. 082 */ 083 public Notifier(Runnable run) { 084 requireNonNullParam(run, "run", "Notifier"); 085 086 m_handler = run; 087 m_notifier.set(NotifierJNI.initializeNotifier()); 088 089 m_thread = 090 new Thread( 091 () -> { 092 while (!Thread.interrupted()) { 093 int notifier = m_notifier.get(); 094 if (notifier == 0) { 095 break; 096 } 097 long curTime = NotifierJNI.waitForNotifierAlarm(notifier); 098 if (curTime == 0) { 099 break; 100 } 101 102 Runnable handler; 103 m_processLock.lock(); 104 try { 105 handler = m_handler; 106 if (m_periodic) { 107 m_expirationTimeSeconds += m_periodSeconds; 108 updateAlarm(); 109 } else { 110 // need to update the alarm to cause it to wait again 111 updateAlarm((long) -1); 112 } 113 } finally { 114 m_processLock.unlock(); 115 } 116 117 if (handler != null) { 118 handler.run(); 119 } 120 } 121 }); 122 m_thread.setName("Notifier"); 123 m_thread.setDaemon(true); 124 m_thread.setUncaughtExceptionHandler( 125 (thread, error) -> { 126 Throwable cause = error.getCause(); 127 if (cause != null) { 128 error = cause; 129 } 130 DriverStation.reportError( 131 "Unhandled exception in Notifier thread: " + error.toString(), error.getStackTrace()); 132 DriverStation.reportError( 133 "The Runnable for this Notifier (or methods called by it) should have handled " 134 + "the exception above.\n" 135 + " The above stacktrace can help determine where the error occurred.\n" 136 + " See https://wpilib.org/stacktrace for more information.", 137 false); 138 }); 139 m_thread.start(); 140 } 141 142 /** 143 * Sets the name of the notifier. Used for debugging purposes only. 144 * 145 * @param name Name 146 */ 147 public void setName(String name) { 148 m_thread.setName(name); 149 NotifierJNI.setNotifierName(m_notifier.get(), name); 150 } 151 152 /** 153 * Change the handler function. 154 * 155 * @param handler Handler 156 */ 157 public void setHandler(Runnable handler) { 158 m_processLock.lock(); 159 try { 160 m_handler = handler; 161 } finally { 162 m_processLock.unlock(); 163 } 164 } 165 166 /** 167 * Register for single event notification. A timer event is queued for a single event after the 168 * specified delay. 169 * 170 * @param delaySeconds Seconds to wait before the handler is called. 171 */ 172 public void startSingle(double delaySeconds) { 173 m_processLock.lock(); 174 try { 175 m_periodic = false; 176 m_periodSeconds = delaySeconds; 177 m_expirationTimeSeconds = RobotController.getFPGATime() * 1e-6 + delaySeconds; 178 updateAlarm(); 179 } finally { 180 m_processLock.unlock(); 181 } 182 } 183 184 /** 185 * Register for periodic event notification. A timer event is queued for periodic event 186 * notification. Each time the interrupt occurs, the event will be immediately requeued for the 187 * same time interval. 188 * 189 * <p>The user-provided callback should be written in a nonblocking manner so the callback can be 190 * recalled at the next periodic event notification. 191 * 192 * @param periodSeconds Period in seconds to call the handler starting one period after the call 193 * to this method. 194 */ 195 public void startPeriodic(double periodSeconds) { 196 m_processLock.lock(); 197 try { 198 m_periodic = true; 199 m_periodSeconds = periodSeconds; 200 m_expirationTimeSeconds = RobotController.getFPGATime() * 1e-6 + periodSeconds; 201 updateAlarm(); 202 } finally { 203 m_processLock.unlock(); 204 } 205 } 206 207 /** 208 * Stop timer events from occurring. Stop any repeating timer events from occurring. This will 209 * also remove any single notification events from the queue. If a timer-based call to the 210 * registered handler is in progress, this function will block until the handler call is complete. 211 */ 212 public void stop() { 213 m_processLock.lock(); 214 try { 215 m_periodic = false; 216 NotifierJNI.cancelNotifierAlarm(m_notifier.get()); 217 } finally { 218 m_processLock.unlock(); 219 } 220 } 221 222 /** 223 * Sets the HAL notifier thread priority. 224 * 225 * <p>The HAL notifier thread is responsible for managing the FPGA's notifier interrupt and waking 226 * up user's Notifiers when it's their time to run. Giving the HAL notifier thread real-time 227 * priority helps ensure the user's real-time Notifiers, if any, are notified to run in a timely 228 * manner. 229 * 230 * @param realTime Set to true to set a real-time priority, false for standard priority. 231 * @param priority Priority to set the thread to. For real-time, this is 1-99 with 99 being 232 * highest. For non-real-time, this is forced to 0. See "man 7 sched" for more details. 233 * @return True on success. 234 */ 235 public static boolean setHALThreadPriority(boolean realTime, int priority) { 236 return NotifierJNI.setHALThreadPriority(realTime, priority); 237 } 238}