[wpilib] Remove InterruptableSensorBase and replace with interrupt classes (#2410)

This commit is contained in:
Thad House
2021-06-05 11:25:21 -07:00
committed by GitHub
parent 15c521a7fe
commit 5c817082a0
28 changed files with 982 additions and 1497 deletions

View File

@@ -0,0 +1,136 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.wpilibj;
import static edu.wpi.first.wpilibj.util.ErrorMessages.requireNonNullParam;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
/**
* Class for handling asynchrounous interrupts.
*
* <p>By default, interrupts will occur on rising edge. Callbacks are disabled by default, and
* Enable() must be called before they will occur.
*
* <p>Both rising and falling edge can be indiciated if both a rising and falling happen between
* callbacks.
*
* <p>Synchronous interrupts are handled by the SynchronousInterrupt class.
*/
public class AsynchronousInterrupt implements AutoCloseable {
private final BiConsumer<Boolean, Boolean> m_callback;
private final SynchronousInterrupt m_interrupt;
private final AtomicBoolean m_keepRunning = new AtomicBoolean(false);
private Thread m_thread;
/**
* Construct a new asynchronous interrupt using a Digital Source.
*
* <p>At construction, the interrupt will trigger on the rising edge.
*
* <p>Callbacks will not be triggered until enable() is called.
*
* @param source The digital source to use.
* @param callback The callback to call on an interrupt
*/
public AsynchronousInterrupt(DigitalSource source, BiConsumer<Boolean, Boolean> callback) {
m_callback = requireNonNullParam(callback, "callback", "AsynchronousInterrupt");
m_interrupt = new SynchronousInterrupt(source);
}
/**
* Closes the interrupt.
*
* <p>This does not close the associated digital source.
*
* <p>This will disable the interrupt if it is enabled.
*/
@Override
public void close() {
disable();
m_interrupt.close();
}
/**
* Enables interrupt callbacks. Before this, callbacks will not occur. Does nothing if already
* enabled.
*/
public void enable() {
if (m_keepRunning.get()) {
return;
}
m_keepRunning.set(true);
m_thread = new Thread(this::threadMain);
}
/** Disables interrupt callbacks. Does nothing if already disabled. */
public void disable() {
m_keepRunning.set(false);
m_interrupt.wakeupWaitingInterrupt();
if (m_thread != null) {
if (m_thread.isAlive()) {
try {
m_thread.interrupt();
m_thread.join();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
m_thread = null;
}
}
/**
* Set which edges to trigger the interrupt on.
*
* @param risingEdge Trigger on rising edge
* @param fallingEdge Trigger on falling edge
*/
public void setInterruptEdges(boolean risingEdge, boolean fallingEdge) {
m_interrupt.setInterruptEdges(risingEdge, fallingEdge);
}
/**
* Get the timestamp of the last rising edge.
*
* <p>This function does not require the interrupt to be enabled to work.
*
* <p>This only works if rising edge was configured using setInterruptEdges.
*
* @return the timestamp in seconds relative to getFPGATime
*/
public double getRisingTimestamp() {
return m_interrupt.getRisingTimestamp();
}
/**
* Get the timestamp of the last falling edge.
*
* <p>This function does not require the interrupt to be enabled to work.
*
* <p>This only works if falling edge was configured using setInterruptEdges.
*
* @return the timestamp in seconds relative to getFPGATime
*/
public double getFallingTimestamp() {
return m_interrupt.getFallingTimestamp();
}
private void threadMain() {
while (m_keepRunning.get()) {
var result = m_interrupt.waitForInterruptRaw(10, false);
if (!m_keepRunning.get()) {
break;
}
if (result == 0) {
continue;
}
m_callback.accept((result & 0x1) != 0, (result & 0x100) != 0);
}
}
}

View File

@@ -40,9 +40,6 @@ public class DigitalInput extends DigitalSource implements Sendable {
public void close() {
super.close();
SendableRegistry.remove(this);
if (m_interrupt != 0) {
cancelInterrupts();
}
DIOJNI.freeDIOPort(m_handle);
m_handle = 0;
}

View File

@@ -10,8 +10,25 @@ package edu.wpi.first.wpilibj;
* just provides a channel, then a digital input will be constructed and freed when finished for the
* source. The source can either be a digital input or analog trigger but not both.
*/
public abstract class DigitalSource extends InterruptableSensorBase {
public abstract class DigitalSource implements AutoCloseable {
public abstract boolean isAnalogTrigger();
public abstract int getChannel();
/**
* If this is an analog trigger.
*
* @return true if this is an analog trigger.
*/
public abstract int getAnalogTriggerTypeForRouting();
/**
* The channel routing number.
*
* @return channel routing number
*/
public abstract int getPortHandleForRouting();
@Override
public void close() {}
}

View File

@@ -1,50 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.wpilibj;
import edu.wpi.first.hal.InterruptJNI.InterruptJNIHandlerFunction;
/**
* It is recommended that you use this class in conjunction with classes from {@link
* java.util.concurrent.atomic} as these objects are all thread safe.
*
* @param <T> The type of the parameter that should be returned to the the method {@link
* #interruptFired(int, Object)}
*/
public abstract class InterruptHandlerFunction<T> {
/**
* The entry point for the interrupt. When the interrupt fires the {@link #apply(int, Object)}
* method is called. The outer class is provided as an interface to allow the implementer to pass
* a generic object to the interrupt fired method.
*/
private class Function implements InterruptJNIHandlerFunction {
@SuppressWarnings("unchecked")
@Override
public void apply(int interruptAssertedMask, Object param) {
interruptFired(interruptAssertedMask, (T) param);
}
}
final Function m_function = new Function();
/**
* This method is run every time an interrupt is fired.
*
* @param interruptAssertedMask Interrupt Mask
* @param param The parameter provided by overriding the {@link #overridableParameter()} method.
*/
public abstract void interruptFired(int interruptAssertedMask, T param);
/**
* Override this method if you would like to pass a specific parameter to the {@link
* #interruptFired(int, Object)} when it is fired by the interrupt. This method is called once
* when {@link InterruptableSensorBase#requestInterrupts(InterruptHandlerFunction)} is run.
*
* @return The object that should be passed to the interrupt when it runs
*/
public T overridableParameter() {
return null;
}
}

View File

@@ -1,270 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.wpilibj;
import edu.wpi.first.hal.InterruptJNI;
import edu.wpi.first.hal.util.AllocationException;
import java.util.function.Consumer;
/** Base for sensors to be used with interrupts. */
public abstract class InterruptableSensorBase implements AutoCloseable {
@SuppressWarnings("MissingJavadocMethod")
public enum WaitResult {
kTimeout(0x0),
kRisingEdge(0x1),
kFallingEdge(0x100),
kBoth(0x101);
public final int value;
WaitResult(int value) {
this.value = value;
}
public static WaitResult getValue(boolean rising, boolean falling) {
if (rising && falling) {
return kBoth;
} else if (rising) {
return kRisingEdge;
} else if (falling) {
return kFallingEdge;
} else {
return kTimeout;
}
}
}
/** The interrupt resource. */
protected int m_interrupt = InterruptJNI.HalInvalidHandle;
/** Flags if the interrupt being allocated is synchronous. */
protected boolean m_isSynchronousInterrupt;
/** Create a new InterrupatableSensorBase. */
public InterruptableSensorBase() {
m_interrupt = 0;
}
@Override
public void close() {
if (m_interrupt != 0) {
cancelInterrupts();
}
}
/**
* If this is an analog trigger.
*
* @return true if this is an analog trigger.
*/
public abstract int getAnalogTriggerTypeForRouting();
/**
* The channel routing number.
*
* @return channel routing number
*/
public abstract int getPortHandleForRouting();
/**
* Request one of the 8 interrupts asynchronously on this digital input.
*
* @param handler The {@link Consumer} that will be called whenever there is an interrupt on this
* device. Request interrupts in synchronous mode where the user program interrupt handler
* will be called when an interrupt occurs. The default is interrupt on rising edges only.
*/
public void requestInterrupts(Consumer<WaitResult> handler) {
if (m_interrupt != 0) {
throw new AllocationException("The interrupt has already been allocated");
}
allocateInterrupts(false);
assert m_interrupt != 0;
InterruptJNI.requestInterrupts(
m_interrupt, getPortHandleForRouting(), getAnalogTriggerTypeForRouting());
setUpSourceEdge(true, false);
InterruptJNI.attachInterruptHandler(
m_interrupt,
(mask, obj) -> {
// Rising edge result is the interrupt bit set in the byte 0xFF
// Falling edge result is the interrupt bit set in the byte 0xFF00
boolean rising = (mask & 0xFF) != 0;
boolean falling = (mask & 0xFF00) != 0;
handler.accept(WaitResult.getValue(rising, falling));
},
null);
}
/**
* Request one of the 8 interrupts asynchronously on this digital input.
*
* @param handler The {@link InterruptHandlerFunction} that contains the method {@link
* InterruptHandlerFunction#interruptFired(int, Object)} that will be called whenever there is
* an interrupt on this device. Request interrupts in synchronous mode where the user program
* interrupt handler will be called when an interrupt occurs. The default is interrupt on
* rising edges only.
*/
public void requestInterrupts(InterruptHandlerFunction<?> handler) {
if (m_interrupt != 0) {
throw new AllocationException("The interrupt has already been allocated");
}
allocateInterrupts(false);
assert m_interrupt != 0;
InterruptJNI.requestInterrupts(
m_interrupt, getPortHandleForRouting(), getAnalogTriggerTypeForRouting());
setUpSourceEdge(true, false);
InterruptJNI.attachInterruptHandler(
m_interrupt, handler.m_function, handler.overridableParameter());
}
/**
* Request one of the 8 interrupts synchronously on this digital input. Request interrupts in
* synchronous mode where the user program will have to explicitly wait for the interrupt to occur
* using {@link #waitForInterrupt}. The default is interrupt on rising edges only.
*/
public void requestInterrupts() {
if (m_interrupt != 0) {
throw new AllocationException("The interrupt has already been allocated");
}
allocateInterrupts(true);
assert m_interrupt != 0;
InterruptJNI.requestInterrupts(
m_interrupt, getPortHandleForRouting(), getAnalogTriggerTypeForRouting());
setUpSourceEdge(true, false);
}
/**
* Allocate the interrupt.
*
* @param watcher true if the interrupt should be in synchronous mode where the user program will
* have to explicitly wait for the interrupt to occur.
*/
protected void allocateInterrupts(boolean watcher) {
m_isSynchronousInterrupt = watcher;
m_interrupt = InterruptJNI.initializeInterrupts(watcher);
}
/**
* Cancel interrupts on this device. This deallocates all the chipobject structures and disables
* any interrupts.
*/
public void cancelInterrupts() {
if (m_interrupt == 0) {
throw new IllegalStateException("The interrupt is not allocated.");
}
InterruptJNI.cleanInterrupts(m_interrupt);
m_interrupt = 0;
}
/**
* In synchronous mode, wait for the defined interrupt to occur.
*
* @param timeout Timeout in seconds
* @param ignorePrevious If true, ignore interrupts that happened before waitForInterrupt was
* called.
* @return Result of the wait.
*/
public WaitResult waitForInterrupt(double timeout, boolean ignorePrevious) {
if (m_interrupt == 0) {
throw new IllegalStateException("The interrupt is not allocated.");
}
int result = InterruptJNI.waitForInterrupt(m_interrupt, timeout, ignorePrevious);
// Rising edge result is the interrupt bit set in the byte 0xFF
// Falling edge result is the interrupt bit set in the byte 0xFF00
// Set any bit set to be true for that edge, and AND the 2 results
// together to match the existing enum for all interrupts
boolean rising = (result & 0xFF) != 0;
boolean falling = (result & 0xFF00) != 0;
return WaitResult.getValue(rising, falling);
}
/**
* In synchronous mode, wait for the defined interrupt to occur.
*
* @param timeout Timeout in seconds
* @return Result of the wait.
*/
public WaitResult waitForInterrupt(double timeout) {
return waitForInterrupt(timeout, true);
}
/**
* Enable interrupts to occur on this input. Interrupts are disabled when the RequestInterrupt
* call is made. This gives time to do the setup of the other options before starting to field
* interrupts.
*/
public void enableInterrupts() {
if (m_interrupt == 0) {
throw new IllegalStateException("The interrupt is not allocated.");
}
if (m_isSynchronousInterrupt) {
throw new IllegalStateException("You do not need to enable synchronous interrupts");
}
InterruptJNI.enableInterrupts(m_interrupt);
}
/** Disable Interrupts without without deallocating structures. */
public void disableInterrupts() {
if (m_interrupt == 0) {
throw new IllegalStateException("The interrupt is not allocated.");
}
if (m_isSynchronousInterrupt) {
throw new IllegalStateException("You can not disable synchronous interrupts");
}
InterruptJNI.disableInterrupts(m_interrupt);
}
/**
* Return the timestamp for the rising interrupt that occurred most recently. This is in the same
* time domain as getClock(). The rising-edge interrupt should be enabled with {@link
* #setUpSourceEdge}.
*
* @return Timestamp in seconds since boot.
*/
public double readRisingTimestamp() {
if (m_interrupt == 0) {
throw new IllegalStateException("The interrupt is not allocated.");
}
return InterruptJNI.readInterruptRisingTimestamp(m_interrupt) * 1e-6;
}
/**
* Return the timestamp for the falling interrupt that occurred most recently. This is in the same
* time domain as getClock(). The falling-edge interrupt should be enabled with {@link
* #setUpSourceEdge}.
*
* @return Timestamp in seconds since boot.
*/
public double readFallingTimestamp() {
if (m_interrupt == 0) {
throw new IllegalStateException("The interrupt is not allocated.");
}
return InterruptJNI.readInterruptFallingTimestamp(m_interrupt) * 1e-6;
}
/**
* Set which edge to trigger interrupts on.
*
* @param risingEdge true to interrupt on rising edge
* @param fallingEdge true to interrupt on falling edge
*/
public void setUpSourceEdge(boolean risingEdge, boolean fallingEdge) {
if (m_interrupt != 0) {
InterruptJNI.setInterruptUpSourceEdge(m_interrupt, risingEdge, fallingEdge);
} else {
throw new IllegalArgumentException("You must call RequestInterrupts before setUpSourceEdge");
}
}
}

View File

@@ -0,0 +1,159 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.wpilibj;
import static edu.wpi.first.wpilibj.util.ErrorMessages.requireNonNullParam;
import edu.wpi.first.hal.InterruptJNI;
/**
* Class for handling sychronous interrupts.
*
* <p>By default, interrupts will occur on rising edge.
*
* <p>Asynchronous interrupts are handled by the AsynchronousInterrupt class.
*/
public class SynchronousInterrupt implements AutoCloseable {
@SuppressWarnings("PMD.SingularField")
private final DigitalSource m_source;
private final int m_handle;
@SuppressWarnings("JavadocMethod")
public enum WaitResult {
kTimeout(0x0),
kRisingEdge(0x1),
kFallingEdge(0x100),
kBoth(0x101);
@SuppressWarnings("MemberName")
public final int value;
WaitResult(int value) {
this.value = value;
}
/** Create a wait result. */
public static WaitResult getValue(boolean rising, boolean falling) {
if (rising && falling) {
return kBoth;
} else if (rising) {
return kRisingEdge;
} else if (falling) {
return kFallingEdge;
} else {
return kTimeout;
}
}
}
/**
* Constructs a new synchronous interrupt using a DigitalSource.
*
* <p>At construction, the interrupt will trigger on the rising edge.
*
* @param source The digital source to use.
*/
public SynchronousInterrupt(DigitalSource source) {
m_source = requireNonNullParam(source, "source", "SynchronousInterrupt");
m_handle = InterruptJNI.initializeInterrupts();
InterruptJNI.requestInterrupts(
m_handle, m_source.getPortHandleForRouting(), m_source.getAnalogTriggerTypeForRouting());
InterruptJNI.setInterruptUpSourceEdge(m_handle, true, false);
}
/**
* Closes the interrupt.
*
* <p>This does not close the associated digital source.
*/
@Override
public void close() {
InterruptJNI.cleanInterrupts(m_handle);
}
/**
* Wait for interrupt that returns the raw result value from the hardware.
*
* <p>Used by AsynchronousInterrupt. Users prefer to use waitForInterrupt.
*
* @param timeoutSeconds The timeout in seconds. 0 or less will return immediately.
* @param ignorePrevious True to ignore if a previous interrupt has occured, and only wait for a
* new trigger. False will consider if an interrupt has occured since the last time the
* interrupt was read.
* @return The raw hardware interrupt result
*/
int waitForInterruptRaw(double timeoutSeconds, boolean ignorePrevious) {
return InterruptJNI.waitForInterrupt(m_handle, timeoutSeconds, ignorePrevious);
}
/**
* Wait for an interrupt.
*
* @param timeoutSeconds The timeout in seconds. 0 or less will return immediately.
* @param ignorePrevious True to ignore if a previous interrupt has occured, and only wait for a
* new trigger. False will consider if an interrupt has occured since the last time the
* interrupt was read.
* @return Result of which edges were triggered, or if an timeout occured.
*/
public WaitResult waitForInterrupt(double timeoutSeconds, boolean ignorePrevious) {
int result = InterruptJNI.waitForInterrupt(m_handle, timeoutSeconds, ignorePrevious);
// Rising edge result is the interrupt bit set in the byte 0xFF
// Falling edge result is the interrupt bit set in the byte 0xFF00
// Set any bit set to be true for that edge, and AND the 2 results
// together to match the existing enum for all interrupts
boolean rising = (result & 0xFF) != 0;
boolean falling = (result & 0xFF00) != 0;
return WaitResult.getValue(rising, falling);
}
/**
* Wait for an interrupt, ingoring any previously occuring interrupts.
*
* @param timeoutSeconds The timeout in seconds. 0 or less will return immediately.
* @return Result of which edges were triggered, or if an timeout occured.
*/
public WaitResult waitForInterrupt(double timeoutSeconds) {
return waitForInterrupt(timeoutSeconds, true);
}
/**
* Set which edges to trigger the interrupt on.
*
* @param risingEdge Trigger on rising edge
* @param fallingEdge Trigger on falling edge
*/
public void setInterruptEdges(boolean risingEdge, boolean fallingEdge) {
InterruptJNI.setInterruptUpSourceEdge(m_handle, risingEdge, fallingEdge);
}
/**
* Get the timestamp of the last rising edge.
*
* <p>This only works if rising edge was configured using setInterruptEdges.
*
* @return the timestamp in seconds relative to getFPGATime
*/
public double getRisingTimestamp() {
return InterruptJNI.readInterruptRisingTimestamp(m_handle) * 1e-6;
}
/**
* Get the timestamp of the last falling edge.
*
* <p>This only works if falling edge was configured using setInterruptEdges.
*
* @return the timestamp in seconds relative to getFPGATime
*/
public double getFallingTimestamp() {
return InterruptJNI.readInterruptFallingTimestamp(m_handle) * 1e-6;
}
/** Force triggering of any waiting interrupt, which will be seen as a timeout. */
public void wakeupWaitingInterrupt() {
InterruptJNI.releaseWaitingInterrupt(m_handle);
}
}