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