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