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 java.util.Objects.requireNonNull; 008 009import edu.wpi.first.hal.NotifierJNI; 010import java.util.concurrent.atomic.AtomicInteger; 011import java.util.concurrent.locks.ReentrantLock; 012 013public class Notifier implements AutoCloseable { 014 // The thread waiting on the HAL alarm. 015 private Thread m_thread; 016 // The lock for the process information. 017 private final ReentrantLock m_processLock = new ReentrantLock(); 018 // The C pointer to the notifier object. We don't use it directly, it is 019 // just passed to the JNI bindings. 020 private final AtomicInteger m_notifier = new AtomicInteger(); 021 // The time, in seconds, at which the corresponding handler should be 022 // called. Has the same zero as RobotController.getFPGATime(). 023 private double m_expirationTimeSeconds; 024 // The handler passed in by the user which should be called at the 025 // appropriate interval. 026 private Runnable m_handler; 027 // Whether we are calling the handler just once or periodically. 028 private boolean m_periodic; 029 // If periodic, the period of the calling; if just once, stores how long it 030 // is until we call the handler. 031 private double m_periodSeconds; 032 033 @Override 034 @SuppressWarnings("NoFinalizer") 035 protected void finalize() { 036 close(); 037 } 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 requireNonNull(run); 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: " + error.toString(), error.getStackTrace()); 132 DriverStation.reportError( 133 "The loopFunc() method (or methods called by it) should have handled " 134 + "the exception above.", 135 false); 136 }); 137 m_thread.start(); 138 } 139 140 /** 141 * Sets the name of the notifier. Used for debugging purposes only. 142 * 143 * @param name Name 144 */ 145 public void setName(String name) { 146 m_thread.setName(name); 147 NotifierJNI.setNotifierName(m_notifier.get(), name); 148 } 149 150 /** 151 * Change the handler function. 152 * 153 * @param handler Handler 154 */ 155 public void setHandler(Runnable handler) { 156 m_processLock.lock(); 157 try { 158 m_handler = handler; 159 } finally { 160 m_processLock.unlock(); 161 } 162 } 163 164 /** 165 * Register for single event notification. A timer event is queued for a single event after the 166 * specified delay. 167 * 168 * @param delaySeconds Seconds to wait before the handler is called. 169 */ 170 public void startSingle(double delaySeconds) { 171 m_processLock.lock(); 172 try { 173 m_periodic = false; 174 m_periodSeconds = delaySeconds; 175 m_expirationTimeSeconds = RobotController.getFPGATime() * 1e-6 + delaySeconds; 176 updateAlarm(); 177 } finally { 178 m_processLock.unlock(); 179 } 180 } 181 182 /** 183 * Register for periodic event notification. A timer event is queued for periodic event 184 * notification. Each time the interrupt occurs, the event will be immediately requeued for the 185 * same time interval. 186 * 187 * @param periodSeconds Period in seconds to call the handler starting one period after the call 188 * to this method. 189 */ 190 public void startPeriodic(double periodSeconds) { 191 m_processLock.lock(); 192 try { 193 m_periodic = true; 194 m_periodSeconds = periodSeconds; 195 m_expirationTimeSeconds = RobotController.getFPGATime() * 1e-6 + periodSeconds; 196 updateAlarm(); 197 } finally { 198 m_processLock.unlock(); 199 } 200 } 201 202 /** 203 * Stop timer events from occurring. Stop any repeating timer events from occurring. This will 204 * also remove any single notification events from the queue. If a timer-based call to the 205 * registered handler is in progress, this function will block until the handler call is complete. 206 */ 207 public void stop() { 208 m_processLock.lock(); 209 try { 210 m_periodic = false; 211 NotifierJNI.cancelNotifierAlarm(m_notifier.get()); 212 } finally { 213 m_processLock.unlock(); 214 } 215 } 216 217 /** 218 * Sets the HAL notifier thread priority. 219 * 220 * <p>The HAL notifier thread is responsible for managing the FPGA's notifier interrupt and waking 221 * up user's Notifiers when it's their time to run. Giving the HAL notifier thread real-time 222 * priority helps ensure the user's real-time Notifiers, if any, are notified to run in a timely 223 * manner. 224 * 225 * @param realTime Set to true to set a real-time priority, false for standard priority. 226 * @param priority Priority to set the thread to. For real-time, this is 1-99 with 99 being 227 * highest. For non-real-time, this is forced to 0. See "man 7 sched" for more details. 228 * @return True on success. 229 */ 230 public static boolean setHALThreadPriority(boolean realTime, int priority) { 231 return NotifierJNI.setHALThreadPriority(realTime, priority); 232 } 233}