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.wpilibj.SynchronousInterrupt.WaitResult; 010import java.util.concurrent.atomic.AtomicBoolean; 011import java.util.function.BiConsumer; 012 013/** 014 * Class for handling asynchronous interrupts using a callback thread. 015 * 016 * <p>By default, interrupts will occur on rising edge. Callbacks are disabled by default, and 017 * enable() must be called before they will occur. 018 * 019 * <p>Both rising and falling edges can be indicated in one callback if both a rising and falling 020 * edge occurred since the previous callback. 021 * 022 * <p>Synchronous (blocking) interrupts are handled by the SynchronousInterrupt class. 023 */ 024public class AsynchronousInterrupt implements AutoCloseable { 025 private final BiConsumer<Boolean, Boolean> m_callback; 026 private final SynchronousInterrupt m_interrupt; 027 028 private final AtomicBoolean m_keepRunning = new AtomicBoolean(false); 029 private Thread m_thread; 030 031 /** 032 * Construct a new asynchronous interrupt using a Digital Source. 033 * 034 * <p>At construction, the interrupt will trigger on the rising edge. 035 * 036 * <p>Callbacks will not be triggered until enable() is called. 037 * 038 * <p>The first bool in the callback indicates the rising edge triggered the interrupt, the second 039 * bool is falling edge. 040 * 041 * @param source The digital source to use. 042 * @param callback The callback to call on an interrupt 043 */ 044 public AsynchronousInterrupt(DigitalSource source, BiConsumer<Boolean, Boolean> callback) { 045 m_callback = requireNonNullParam(callback, "callback", "AsynchronousInterrupt"); 046 m_interrupt = new SynchronousInterrupt(source); 047 } 048 049 /** 050 * Closes the interrupt. 051 * 052 * <p>This does not close the associated digital source. 053 * 054 * <p>This will disable the interrupt if it is enabled. 055 */ 056 @Override 057 public void close() { 058 disable(); 059 m_interrupt.close(); 060 } 061 062 /** 063 * Enables interrupt callbacks. Before this, callbacks will not occur. Does nothing if already 064 * enabled. 065 */ 066 public void enable() { 067 if (m_keepRunning.get()) { 068 return; 069 } 070 071 m_keepRunning.set(true); 072 m_thread = new Thread(this::threadMain); 073 m_thread.start(); 074 } 075 076 /** Disables interrupt callbacks. Does nothing if already disabled. */ 077 public void disable() { 078 m_keepRunning.set(false); 079 m_interrupt.wakeupWaitingInterrupt(); 080 if (m_thread != null) { 081 if (m_thread.isAlive()) { 082 try { 083 m_thread.interrupt(); 084 m_thread.join(); 085 } catch (InterruptedException ex) { 086 Thread.currentThread().interrupt(); 087 } 088 } 089 m_thread = null; 090 } 091 } 092 093 /** 094 * Set which edges to trigger the interrupt on. 095 * 096 * @param risingEdge Trigger on rising edge 097 * @param fallingEdge Trigger on falling edge 098 */ 099 public void setInterruptEdges(boolean risingEdge, boolean fallingEdge) { 100 m_interrupt.setInterruptEdges(risingEdge, fallingEdge); 101 } 102 103 /** 104 * Get the timestamp of the last rising edge. 105 * 106 * <p>This function does not require the interrupt to be enabled to work. 107 * 108 * <p>This only works if rising edge was configured using setInterruptEdges. 109 * 110 * @return the timestamp in seconds relative to getFPGATime 111 */ 112 public double getRisingTimestamp() { 113 return m_interrupt.getRisingTimestamp(); 114 } 115 116 /** 117 * Get the timestamp of the last falling edge. 118 * 119 * <p>This function does not require the interrupt to be enabled to work. 120 * 121 * <p>This only works if falling edge was configured using setInterruptEdges. 122 * 123 * @return the timestamp in seconds relative to getFPGATime 124 */ 125 public double getFallingTimestamp() { 126 return m_interrupt.getFallingTimestamp(); 127 } 128 129 private void threadMain() { 130 while (m_keepRunning.get()) { 131 var result = m_interrupt.waitForInterrupt(10, false); 132 if (!m_keepRunning.get()) { 133 break; 134 } 135 if (result == WaitResult.kTimeout) { 136 continue; 137 } 138 139 boolean rising = false; 140 boolean falling = false; 141 switch (result) { 142 case kBoth: 143 rising = true; 144 falling = true; 145 break; 146 case kFallingEdge: 147 falling = true; 148 break; 149 case kRisingEdge: 150 rising = true; 151 break; 152 default: 153 break; 154 } 155 156 m_callback.accept(rising, falling); 157 } 158 } 159}