001/*----------------------------------------------------------------------------*/
002/* Copyright (c) 2008-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 edu.wpi.first.wpilibj.hal.InterruptJNI;
011import edu.wpi.first.wpilibj.util.AllocationException;
012
013
014/**
015 * Base for sensors to be used with interrupts.
016 */
017public abstract class InterruptableSensorBase extends SensorBase {
018  @SuppressWarnings("JavadocMethod")
019  public enum WaitResult {
020    kTimeout(0x0), kRisingEdge(0x1), kFallingEdge(0x100), kBoth(0x101);
021
022    @SuppressWarnings("MemberName")
023    public final int value;
024
025    WaitResult(int value) {
026      this.value = value;
027    }
028  }
029
030  /**
031   * The interrupt resource.
032   */
033  protected int m_interrupt = InterruptJNI.HalInvalidHandle;
034
035  /**
036   * Flags if the interrupt being allocated is synchronous.
037   */
038  protected boolean m_isSynchronousInterrupt = false;
039
040  /**
041   * Create a new InterrupatableSensorBase.
042   */
043  public InterruptableSensorBase() {
044    m_interrupt = 0;
045  }
046
047  /**
048   * Frees the resources for this output.
049   */
050  @Override
051  public void free() {
052    super.free();
053    if (m_interrupt != 0) {
054      cancelInterrupts();
055    }
056  }
057
058  /**
059   * If this is an analog trigger.
060   *
061   * @return true if this is an analog trigger.
062   */
063  public abstract int getAnalogTriggerTypeForRouting();
064
065  /**
066   * The channel routing number.
067   *
068   * @return channel routing number
069   */
070  public abstract int getPortHandleForRouting();
071
072  /**
073   * Request one of the 8 interrupts asynchronously on this digital input.
074   *
075   * @param handler The {@link InterruptHandlerFunction} that contains the method {@link
076   *                InterruptHandlerFunction#interruptFired(int, Object)} that will be called
077   *                whenever there is an interrupt on this device. Request interrupts in synchronous
078   *                mode where the user program interrupt handler will be called when an interrupt
079   *                occurs. The default is interrupt on rising edges only.
080   */
081  public void requestInterrupts(InterruptHandlerFunction<?> handler) {
082    if (m_interrupt != 0) {
083      throw new AllocationException("The interrupt has already been allocated");
084    }
085
086    allocateInterrupts(false);
087
088    assert m_interrupt != 0;
089
090    InterruptJNI.requestInterrupts(m_interrupt, getPortHandleForRouting(),
091        getAnalogTriggerTypeForRouting());
092    setUpSourceEdge(true, false);
093    InterruptJNI.attachInterruptHandler(m_interrupt, handler.m_function,
094        handler.overridableParameter());
095  }
096
097  /**
098   * Request one of the 8 interrupts synchronously on this digital input. Request interrupts in
099   * synchronous mode where the user program will have to explicitly wait for the interrupt to occur
100   * using {@link #waitForInterrupt}. The default is interrupt on rising edges only.
101   */
102  public void requestInterrupts() {
103    if (m_interrupt != 0) {
104      throw new AllocationException("The interrupt has already been allocated");
105    }
106
107    allocateInterrupts(true);
108
109    assert m_interrupt != 0;
110
111    InterruptJNI.requestInterrupts(m_interrupt, getPortHandleForRouting(),
112        getAnalogTriggerTypeForRouting());
113    setUpSourceEdge(true, false);
114
115  }
116
117  /**
118   * Allocate the interrupt.
119   *
120   * @param watcher true if the interrupt should be in synchronous mode where the user program will
121   *                have to explicitly wait for the interrupt to occur.
122   */
123  protected void allocateInterrupts(boolean watcher) {
124    m_isSynchronousInterrupt = watcher;
125
126    m_interrupt = InterruptJNI.initializeInterrupts(watcher);
127  }
128
129  /**
130   * Cancel interrupts on this device. This deallocates all the chipobject structures and disables
131   * any interrupts.
132   */
133  public void cancelInterrupts() {
134    if (m_interrupt == 0) {
135      throw new IllegalStateException("The interrupt is not allocated.");
136    }
137    InterruptJNI.cleanInterrupts(m_interrupt);
138    m_interrupt = 0;
139  }
140
141  /**
142   * In synchronous mode, wait for the defined interrupt to occur.
143   *
144   * @param timeout        Timeout in seconds
145   * @param ignorePrevious If true, ignore interrupts that happened before waitForInterrupt was
146   *                       called.
147   * @return Result of the wait.
148   */
149  public WaitResult waitForInterrupt(double timeout, boolean ignorePrevious) {
150    if (m_interrupt == 0) {
151      throw new IllegalStateException("The interrupt is not allocated.");
152    }
153    int result = InterruptJNI.waitForInterrupt(m_interrupt, timeout, ignorePrevious);
154
155    // Rising edge result is the interrupt bit set in the byte 0xFF
156    // Falling edge result is the interrupt bit set in the byte 0xFF00
157    // Set any bit set to be true for that edge, and AND the 2 results
158    // together to match the existing enum for all interrupts
159    int rising = ((result & 0xFF) != 0) ? 0x1 : 0x0;
160    int falling = ((result & 0xFF00) != 0) ? 0x0100 : 0x0;
161    result = rising | falling;
162
163    for (WaitResult mode : WaitResult.values()) {
164      if (mode.value == result) {
165        return mode;
166      }
167    }
168    return null;
169  }
170
171  /**
172   * In synchronous mode, wait for the defined interrupt to occur.
173   *
174   * @param timeout Timeout in seconds
175   * @return Result of the wait.
176   */
177  public WaitResult waitForInterrupt(double timeout) {
178    return waitForInterrupt(timeout, true);
179  }
180
181  /**
182   * Enable interrupts to occur on this input. Interrupts are disabled when the RequestInterrupt
183   * call is made. This gives time to do the setup of the other options before starting to field
184   * interrupts.
185   */
186  public void enableInterrupts() {
187    if (m_interrupt == 0) {
188      throw new IllegalStateException("The interrupt is not allocated.");
189    }
190    if (m_isSynchronousInterrupt) {
191      throw new IllegalStateException("You do not need to enable synchronous interrupts");
192    }
193    InterruptJNI.enableInterrupts(m_interrupt);
194  }
195
196  /**
197   * Disable Interrupts without without deallocating structures.
198   */
199  public void disableInterrupts() {
200    if (m_interrupt == 0) {
201      throw new IllegalStateException("The interrupt is not allocated.");
202    }
203    if (m_isSynchronousInterrupt) {
204      throw new IllegalStateException("You can not disable synchronous interrupts");
205    }
206    InterruptJNI.disableInterrupts(m_interrupt);
207  }
208
209  /**
210   * Return the timestamp for the rising interrupt that occurred most recently. This is in the same
211   * time domain as getClock(). The rising-edge interrupt should be enabled with {@link
212   * #setUpSourceEdge}.
213   *
214   * @return Timestamp in seconds since boot.
215   */
216  public double readRisingTimestamp() {
217    if (m_interrupt == 0) {
218      throw new IllegalStateException("The interrupt is not allocated.");
219    }
220    return InterruptJNI.readInterruptRisingTimestamp(m_interrupt);
221  }
222
223  /**
224   * Return the timestamp for the falling interrupt that occurred most recently. This is in the same
225   * time domain as getClock(). The falling-edge interrupt should be enabled with {@link
226   * #setUpSourceEdge}.
227   *
228   * @return Timestamp in seconds since boot.
229   */
230  public double readFallingTimestamp() {
231    if (m_interrupt == 0) {
232      throw new IllegalStateException("The interrupt is not allocated.");
233    }
234    return InterruptJNI.readInterruptFallingTimestamp(m_interrupt);
235  }
236
237  /**
238   * Set which edge to trigger interrupts on.
239   *
240   * @param risingEdge  true to interrupt on rising edge
241   * @param fallingEdge true to interrupt on falling edge
242   */
243  public void setUpSourceEdge(boolean risingEdge, boolean fallingEdge) {
244    if (m_interrupt != 0) {
245      InterruptJNI.setInterruptUpSourceEdge(m_interrupt, risingEdge,
246          fallingEdge);
247    } else {
248      throw new IllegalArgumentException("You must call RequestInterrupts before setUpSourceEdge");
249    }
250  }
251}