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