001/*----------------------------------------------------------------------------*/ 002/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */ 003/* Open Source Software - may be modified and shared by FRC teams. The code */ 004/* must be accompanied by the FIRST BSD license file in the root directory of */ 005/* the project. */ 006/*----------------------------------------------------------------------------*/ 007 008package edu.wpi.first.wpilibj; 009 010import java.util.concurrent.atomic.AtomicInteger; 011import java.util.concurrent.locks.ReentrantLock; 012 013import edu.wpi.first.wpilibj.hal.NotifierJNI; 014 015public class Notifier { 016 // The thread waiting on the HAL alarm. 017 private final Thread m_thread; 018 // The lock for the process information. 019 private final ReentrantLock m_processLock = new ReentrantLock(); 020 // The C pointer to the notifier object. We don't use it directly, it is 021 // just passed to the JNI bindings. 022 private final AtomicInteger m_notifier = new AtomicInteger(); 023 // The time, in microseconds, at which the corresponding handler should be 024 // called. Has the same zero as Utility.getFPGATime(). 025 private double m_expirationTime = 0; 026 // The handler passed in by the user which should be called at the 027 // appropriate interval. 028 private Runnable m_handler; 029 // Whether we are calling the handler just once or periodically. 030 private boolean m_periodic = false; 031 // If periodic, the period of the calling; if just once, stores how long it 032 // is until we call the handler. 033 private double m_period = 0; 034 035 @Override 036 @SuppressWarnings("NoFinalizer") 037 protected void finalize() { 038 int handle = m_notifier.getAndSet(0); 039 NotifierJNI.stopNotifier(handle); 040 // Join the thread to ensure the handler has exited. 041 if (m_thread.isAlive()) { 042 try { 043 m_thread.interrupt(); 044 m_thread.join(); 045 } catch (InterruptedException ex) { 046 Thread.currentThread().interrupt(); 047 } 048 } 049 NotifierJNI.cleanNotifier(handle); 050 } 051 052 /** 053 * Update the alarm hardware to reflect the next alarm. 054 */ 055 private void updateAlarm() { 056 int notifier = m_notifier.get(); 057 if (notifier == 0) { 058 return; 059 } 060 NotifierJNI.updateNotifierAlarm(notifier, (long) (m_expirationTime * 1e6)); 061 } 062 063 /** 064 * Create a Notifier for timer event notification. 065 * 066 * @param run The handler that is called at the notification time which is set using StartSingle 067 * or StartPeriodic. 068 */ 069 public Notifier(Runnable run) { 070 m_handler = run; 071 m_notifier.set(NotifierJNI.initializeNotifier()); 072 073 m_thread = new Thread(() -> { 074 while (!Thread.interrupted()) { 075 int notifier = m_notifier.get(); 076 if (notifier == 0) { 077 break; 078 } 079 long curTime = NotifierJNI.waitForNotifierAlarm(notifier); 080 if (curTime == 0) { 081 break; 082 } 083 084 Runnable handler = null; 085 m_processLock.lock(); 086 try { 087 handler = m_handler; 088 if (m_periodic) { 089 m_expirationTime += m_period; 090 updateAlarm(); 091 } 092 } finally { 093 m_processLock.unlock(); 094 } 095 096 if (handler != null) { 097 handler.run(); 098 } 099 } 100 }); 101 m_thread.setDaemon(true); 102 m_thread.start(); 103 } 104 105 /** 106 * Change the handler function. 107 * 108 * @param handler Handler 109 */ 110 public void setHandler(Runnable handler) { 111 m_processLock.lock(); 112 try { 113 m_handler = handler; 114 } finally { 115 m_processLock.unlock(); 116 } 117 } 118 119 /** 120 * Register for single event notification. A timer event is queued for a single event after the 121 * specified delay. 122 * 123 * @param delay Seconds to wait before the handler is called. 124 */ 125 public void startSingle(double delay) { 126 m_processLock.lock(); 127 try { 128 m_periodic = false; 129 m_period = delay; 130 m_expirationTime = RobotController.getFPGATime() * 1e-6 + delay; 131 updateAlarm(); 132 } finally { 133 m_processLock.unlock(); 134 } 135 } 136 137 /** 138 * Register for periodic event notification. A timer event is queued for periodic event 139 * notification. Each time the interrupt occurs, the event will be immediately requeued for the 140 * same time interval. 141 * 142 * @param period Period in seconds to call the handler starting one period after the call to this 143 * method. 144 */ 145 public void startPeriodic(double period) { 146 m_processLock.lock(); 147 try { 148 m_periodic = true; 149 m_period = period; 150 m_expirationTime = RobotController.getFPGATime() * 1e-6 + period; 151 updateAlarm(); 152 } finally { 153 m_processLock.unlock(); 154 } 155 } 156 157 /** 158 * Stop timer events from occurring. Stop any repeating timer events from occurring. This will 159 * also remove any single notification events from the queue. If a timer-based call to the 160 * registered handler is in progress, this function will block until the handler call is complete. 161 */ 162 public void stop() { 163 NotifierJNI.cancelNotifierAlarm(m_notifier.get()); 164 } 165}