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.InterruptJNI;
010
011/**
012 * Class for handling synchronous (blocking) interrupts.
013 *
014 * <p>By default, interrupts will occur on rising edge.
015 *
016 * <p>Asynchronous interrupts are handled by the AsynchronousInterrupt class.
017 */
018public class SynchronousInterrupt implements AutoCloseable {
019  @SuppressWarnings("PMD.SingularField")
020  private final DigitalSource m_source;
021
022  private final int m_handle;
023
024  /** Event trigger combinations for a synchronous interrupt. */
025  public enum WaitResult {
026    kTimeout(0x0),
027    kRisingEdge(0x1),
028    kFallingEdge(0x100),
029    kBoth(0x101);
030
031    public final int value;
032
033    WaitResult(int value) {
034      this.value = value;
035    }
036
037    /**
038     * Create a wait result enum.
039     *
040     * @param rising True if a rising edge occurred.
041     * @param falling True if a falling edge occurred.
042     * @return A wait result enum.
043     */
044    public static WaitResult getValue(boolean rising, boolean falling) {
045      if (rising && falling) {
046        return kBoth;
047      } else if (rising) {
048        return kRisingEdge;
049      } else if (falling) {
050        return kFallingEdge;
051      } else {
052        return kTimeout;
053      }
054    }
055  }
056
057  /**
058   * Constructs a new synchronous interrupt using a DigitalSource.
059   *
060   * <p>At construction, the interrupt will trigger on the rising edge.
061   *
062   * @param source The digital source to use.
063   */
064  public SynchronousInterrupt(DigitalSource source) {
065    m_source = requireNonNullParam(source, "source", "SynchronousInterrupt");
066    m_handle = InterruptJNI.initializeInterrupts();
067    InterruptJNI.requestInterrupts(
068        m_handle, m_source.getPortHandleForRouting(), m_source.getAnalogTriggerTypeForRouting());
069    InterruptJNI.setInterruptUpSourceEdge(m_handle, true, false);
070  }
071
072  /**
073   * Closes the interrupt.
074   *
075   * <p>This does not close the associated digital source.
076   */
077  @Override
078  public void close() {
079    InterruptJNI.cleanInterrupts(m_handle);
080  }
081
082  /**
083   * Wait for interrupt that returns the raw result value from the hardware.
084   *
085   * <p>Used by AsynchronousInterrupt. Users should use waitForInterrupt.
086   *
087   * @param timeoutSeconds The timeout in seconds. 0 or less will return immediately.
088   * @param ignorePrevious True to ignore if a previous interrupt has occurred, and only wait for a
089   *     new trigger. False will consider if an interrupt has occurred since the last time the
090   *     interrupt was read.
091   * @return The raw hardware interrupt result
092   */
093  long waitForInterruptRaw(double timeoutSeconds, boolean ignorePrevious) {
094    return InterruptJNI.waitForInterrupt(m_handle, timeoutSeconds, ignorePrevious);
095  }
096
097  /**
098   * Wait for an interrupt.
099   *
100   * @param timeoutSeconds The timeout in seconds. 0 or less will return immediately.
101   * @param ignorePrevious True to ignore if a previous interrupt has occurred, and only wait for a
102   *     new trigger. False will consider if an interrupt has occurred since the last time the
103   *     interrupt was read.
104   * @return Result of which edges were triggered, or if a timeout occurred.
105   */
106  public WaitResult waitForInterrupt(double timeoutSeconds, boolean ignorePrevious) {
107    long result = InterruptJNI.waitForInterrupt(m_handle, timeoutSeconds, ignorePrevious);
108
109    // Rising edge result is the interrupt bit set in the byte 0xFF
110    // Falling edge result is the interrupt bit set in the byte 0xFF00
111    // Set any bit set to be true for that edge, and then conduct a logical AND on the 2 results
112    // together to match the existing enum for all interrupts
113    boolean rising = (result & 0xFF) != 0;
114    boolean falling = (result & 0xFF00) != 0;
115    return WaitResult.getValue(rising, falling);
116  }
117
118  /**
119   * Wait for an interrupt, ignoring any previously occurring interrupts.
120   *
121   * @param timeoutSeconds The timeout in seconds. 0 or less will return immediately.
122   * @return Result of which edges were triggered, or if a timeout occurred.
123   */
124  public WaitResult waitForInterrupt(double timeoutSeconds) {
125    return waitForInterrupt(timeoutSeconds, true);
126  }
127
128  /**
129   * Set which edges to trigger the interrupt on.
130   *
131   * @param risingEdge Trigger on rising edge
132   * @param fallingEdge Trigger on falling edge
133   */
134  public void setInterruptEdges(boolean risingEdge, boolean fallingEdge) {
135    InterruptJNI.setInterruptUpSourceEdge(m_handle, risingEdge, fallingEdge);
136  }
137
138  /**
139   * Get the timestamp of the last rising edge.
140   *
141   * <p>This only works if rising edge was configured using setInterruptEdges.
142   *
143   * @return the timestamp in seconds relative to getFPGATime
144   */
145  public double getRisingTimestamp() {
146    return InterruptJNI.readInterruptRisingTimestamp(m_handle) * 1e-6;
147  }
148
149  /**
150   * Get the timestamp of the last falling edge.
151   *
152   * <p>This only works if falling edge was configured using setInterruptEdges.
153   *
154   * @return the timestamp in seconds relative to getFPGATime
155   */
156  public double getFallingTimestamp() {
157    return InterruptJNI.readInterruptFallingTimestamp(m_handle) * 1e-6;
158  }
159
160  /** Force triggering of any waiting interrupt, which will be seen as a timeout. */
161  public void wakeupWaitingInterrupt() {
162    InterruptJNI.releaseWaitingInterrupt(m_handle);
163  }
164}