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}