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; 008import static org.wpilib.util.ErrorMessages.requireNonNullParam; 009 010import java.util.concurrent.atomic.AtomicInteger; 011import java.util.concurrent.locks.ReentrantLock; 012import org.wpilib.driverstation.DriverStation; 013import org.wpilib.hardware.hal.NotifierJNI; 014import org.wpilib.units.measure.Frequency; 015import org.wpilib.units.measure.Time; 016import org.wpilib.util.WPIUtilJNI; 017 018/** 019 * Notifiers run a user-provided callback function on a separate thread. 020 * 021 * <p>If startSingle() is used, the callback will run once. If startPeriodic() is used, the callback 022 * will run repeatedly with the given period until stop() is called. 023 */ 024public class Notifier implements AutoCloseable { 025 // The thread waiting on the HAL alarm. 026 private Thread m_thread; 027 028 // The lock held while updating process information. 029 private final ReentrantLock m_processLock = new ReentrantLock(); 030 031 // HAL handle passed to the JNI bindings (atomic for proper destruction). 032 private final AtomicInteger m_notifier = new AtomicInteger(); 033 034 // The user-provided callback. 035 private Runnable m_callback; 036 037 @Override 038 public void close() { 039 int handle = m_notifier.getAndSet(0); 040 if (handle == 0) { 041 return; 042 } 043 NotifierJNI.destroyNotifier(handle); 044 // Join the thread to ensure the callback has exited. 045 if (m_thread.isAlive()) { 046 try { 047 m_thread.interrupt(); 048 m_thread.join(); 049 } catch (InterruptedException ex) { 050 Thread.currentThread().interrupt(); 051 } 052 } 053 m_thread = null; 054 } 055 056 /** 057 * Create a Notifier with the given callback. 058 * 059 * <p>Configure when the callback runs with startSingle() or startPeriodic(). 060 * 061 * @param callback The callback to run. 062 */ 063 public Notifier(Runnable callback) { 064 requireNonNullParam(callback, "callback", "Notifier"); 065 066 m_callback = callback; 067 m_notifier.set(NotifierJNI.createNotifier()); 068 069 m_thread = 070 new Thread( 071 () -> { 072 while (!Thread.interrupted()) { 073 int notifier = m_notifier.get(); 074 if (notifier == 0) { 075 break; 076 } 077 try { 078 WPIUtilJNI.waitForObject(notifier); 079 } catch (InterruptedException ex) { 080 Thread.currentThread().interrupt(); 081 break; 082 } 083 084 Runnable threadHandler; 085 m_processLock.lock(); 086 try { 087 threadHandler = m_callback; 088 } finally { 089 m_processLock.unlock(); 090 } 091 092 // Call callback 093 if (threadHandler != null) { 094 threadHandler.run(); 095 } 096 097 // Acknowledge the alarm 098 NotifierJNI.acknowledgeNotifierAlarm(notifier); 099 } 100 }); 101 m_thread.setName("Notifier"); 102 m_thread.setDaemon(true); 103 m_thread.setUncaughtExceptionHandler( 104 (thread, error) -> { 105 Throwable cause = error.getCause(); 106 if (cause != null) { 107 error = cause; 108 } 109 DriverStation.reportError( 110 "Unhandled exception in Notifier thread: " + error, error.getStackTrace()); 111 DriverStation.reportError( 112 "The Runnable for this Notifier (or methods called by it) should have handled " 113 + "the exception above.\n" 114 + " The above stacktrace can help determine where the error occurred.\n" 115 + " See https://wpilib.org/stacktrace for more information.", 116 false); 117 }); 118 m_thread.start(); 119 } 120 121 /** 122 * Sets the name of the notifier. Used for debugging purposes only. 123 * 124 * @param name Name 125 */ 126 public void setName(String name) { 127 m_thread.setName(name); 128 NotifierJNI.setNotifierName(m_notifier.get(), name); 129 } 130 131 /** 132 * Change the callback function. 133 * 134 * @param callback The callback function. 135 */ 136 public void setCallback(Runnable callback) { 137 m_processLock.lock(); 138 try { 139 m_callback = callback; 140 } finally { 141 m_processLock.unlock(); 142 } 143 } 144 145 /** 146 * Run the callback once after the given delay. 147 * 148 * @param delay Time in seconds to wait before the callback is called. 149 */ 150 public void startSingle(double delay) { 151 NotifierJNI.setNotifierAlarm(m_notifier.get(), (long) (delay * 1e6), 0, false, false); 152 } 153 154 /** 155 * Run the callback once after the given delay. 156 * 157 * @param delay Time to wait before the callback is called. 158 */ 159 public void startSingle(Time delay) { 160 startSingle(delay.in(Seconds)); 161 } 162 163 /** 164 * Run the callback periodically with the given period. 165 * 166 * <p>The user-provided callback should be written so that it completes before the next time it's 167 * scheduled to run. 168 * 169 * @param period Period in seconds after which to call the callback starting one period after the 170 * call to this method. 171 */ 172 public void startPeriodic(double period) { 173 long periodMicroS = (long) (period * 1e6); 174 NotifierJNI.setNotifierAlarm(m_notifier.get(), periodMicroS, periodMicroS, false, false); 175 } 176 177 /** 178 * Run the callback periodically with the given period. 179 * 180 * <p>The user-provided callback should be written so that it completes before the next time it's 181 * scheduled to run. 182 * 183 * @param period Period after which to call the callback starting one period after the call to 184 * this method. 185 */ 186 public void startPeriodic(Time period) { 187 startPeriodic(period.in(Seconds)); 188 } 189 190 /** 191 * Run the callback periodically with the given frequency. 192 * 193 * <p>The user-provided callback should be written so that it completes before the next time it's 194 * scheduled to run. 195 * 196 * @param frequency Frequency at which to call the callback, starting one period after the call to 197 * this method. 198 */ 199 public void startPeriodic(Frequency frequency) { 200 startPeriodic(frequency.asPeriod()); 201 } 202 203 /** 204 * Stop further callback invocations. 205 * 206 * <p>No further periodic callbacks will occur. Single invocations will also be cancelled if they 207 * haven't yet occurred. 208 */ 209 public void stop() { 210 NotifierJNI.cancelNotifierAlarm(m_notifier.get(), false); 211 } 212 213 /** 214 * Sets the HAL notifier thread priority. 215 * 216 * <p>The HAL notifier thread is responsible for managing the FPGA's notifier interrupt and waking 217 * up user's Notifiers when it's their time to run. Giving the HAL notifier thread real-time 218 * priority helps ensure the user's real-time Notifiers, if any, are notified to run in a timely 219 * manner. 220 * 221 * @param realTime Set to true to set a real-time priority, false for standard priority. 222 * @param priority Priority to set the thread to. For real-time, this is 1-99 with 99 being 223 * highest. For non-real-time, this is forced to 0. See "man 7 sched" for more details. 224 * @return True on success. 225 */ 226 public static boolean setHALThreadPriority(boolean realTime, int priority) { 227 return NotifierJNI.setHALThreadPriority(realTime, priority); 228 } 229}