mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
[wpilib] Remove InterruptableSensorBase and replace with interrupt classes (#2410)
This commit is contained in:
@@ -8,7 +8,6 @@ import static org.hamcrest.Matchers.both;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.lessThan;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import edu.wpi.first.wpilibj.test.AbstractComsSetup;
|
||||
@@ -20,34 +19,31 @@ import org.junit.Test;
|
||||
|
||||
/**
|
||||
* This class should not be run as a test explicitly. Instead it should be extended by tests that
|
||||
* use the InterruptableSensorBase.
|
||||
* use DigitalSource.
|
||||
*/
|
||||
public abstract class AbstractInterruptTest extends AbstractComsSetup {
|
||||
private InterruptableSensorBase m_interruptable = null;
|
||||
private DigitalSource m_source = null;
|
||||
|
||||
private InterruptableSensorBase getInterruptable() {
|
||||
if (m_interruptable == null) {
|
||||
m_interruptable = giveInterruptableSensorBase();
|
||||
private DigitalSource getSource() {
|
||||
if (m_source == null) {
|
||||
m_source = giveSource();
|
||||
}
|
||||
return m_interruptable;
|
||||
return m_source;
|
||||
}
|
||||
|
||||
@After
|
||||
public void interruptTeardown() {
|
||||
if (m_interruptable != null) {
|
||||
freeInterruptableSensorBase();
|
||||
m_interruptable = null;
|
||||
if (m_source != null) {
|
||||
freeSource();
|
||||
m_source = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Give the interruptable sensor base that interrupts can be attached to. */
|
||||
abstract InterruptableSensorBase giveInterruptableSensorBase();
|
||||
/** Give the sensor source that interrupts can be attached to. */
|
||||
abstract DigitalSource giveSource();
|
||||
|
||||
/**
|
||||
* Cleans up the interruptable sensor base. This is only called if {@link
|
||||
* #giveInterruptableSensorBase()} is called.
|
||||
*/
|
||||
abstract void freeInterruptableSensorBase();
|
||||
/** Cleans up the sensor source. This is only called if {@link #giveSource()} is called. */
|
||||
abstract void freeSource();
|
||||
|
||||
/** Perform whatever action is required to set the interrupt high. */
|
||||
abstract void setInterruptHigh();
|
||||
@@ -55,116 +51,78 @@ public abstract class AbstractInterruptTest extends AbstractComsSetup {
|
||||
/** Perform whatever action is required to set the interrupt low. */
|
||||
abstract void setInterruptLow();
|
||||
|
||||
private class InterruptCounter {
|
||||
private final AtomicInteger m_count = new AtomicInteger();
|
||||
|
||||
void increment() {
|
||||
m_count.addAndGet(1);
|
||||
}
|
||||
|
||||
int getCount() {
|
||||
return m_count.get();
|
||||
}
|
||||
}
|
||||
|
||||
private class TestInterruptHandlerFunction extends InterruptHandlerFunction<InterruptCounter> {
|
||||
protected final AtomicBoolean m_exceptionThrown = new AtomicBoolean(false);
|
||||
/** Stores the time that the interrupt fires. */
|
||||
final AtomicLong m_interruptFireTime = new AtomicLong();
|
||||
/** Stores if the interrupt has completed at least once. */
|
||||
final AtomicBoolean m_interruptComplete = new AtomicBoolean(false);
|
||||
|
||||
protected Exception m_ex;
|
||||
final InterruptCounter m_counter;
|
||||
|
||||
TestInterruptHandlerFunction(InterruptCounter counter) {
|
||||
m_counter = counter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interruptFired(int interruptAssertedMask, InterruptCounter param) {
|
||||
m_interruptFireTime.set(RobotController.getFPGATime());
|
||||
m_counter.increment();
|
||||
try {
|
||||
// This won't cause the test to fail
|
||||
assertSame(m_counter, param);
|
||||
} catch (Exception ex) {
|
||||
// So we must throw the exception within the test
|
||||
m_exceptionThrown.set(true);
|
||||
m_ex = ex;
|
||||
}
|
||||
m_interruptComplete.set(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterruptCounter overridableParameter() {
|
||||
return m_counter;
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 1000)
|
||||
public void testSingleInterruptsTriggering() throws Exception {
|
||||
// Given
|
||||
final InterruptCounter counter = new InterruptCounter();
|
||||
TestInterruptHandlerFunction function = new TestInterruptHandlerFunction(counter);
|
||||
// final InterruptCounter counter = new InterruptCounter();
|
||||
// TestInterruptHandlerFunction function = new
|
||||
// TestInterruptHandlerFunction(counter);
|
||||
|
||||
// When
|
||||
getInterruptable().requestInterrupts(function);
|
||||
getInterruptable().enableInterrupts();
|
||||
AtomicBoolean hasFired = new AtomicBoolean(false);
|
||||
AtomicInteger counter = new AtomicInteger(0);
|
||||
AtomicLong interruptFireTime = new AtomicLong();
|
||||
|
||||
setInterruptLow();
|
||||
Timer.delay(0.01);
|
||||
// Note: Utility.getFPGATime() is used because double values can turn over
|
||||
// after the robot has been running for a long time
|
||||
final long interruptTriggerTime = RobotController.getFPGATime();
|
||||
setInterruptHigh();
|
||||
try (AsynchronousInterrupt interrupt =
|
||||
new AsynchronousInterrupt(
|
||||
getSource(),
|
||||
(a, b) -> {
|
||||
interruptFireTime.set(RobotController.getFPGATime());
|
||||
hasFired.set(true);
|
||||
counter.incrementAndGet();
|
||||
})) {
|
||||
interrupt.enable();
|
||||
setInterruptLow();
|
||||
Timer.delay(0.01);
|
||||
final long interruptTriggerTime = RobotController.getFPGATime();
|
||||
setInterruptHigh();
|
||||
while (!hasFired.get()) {
|
||||
Timer.delay(0.005);
|
||||
}
|
||||
|
||||
// Delay until the interrupt is complete
|
||||
while (!function.m_interruptComplete.get()) {
|
||||
Timer.delay(0.005);
|
||||
assertEquals("The interrupt did not fire the expected number of times", 1, counter.get());
|
||||
|
||||
final long range = 10000; // in microseconds
|
||||
assertThat(
|
||||
"The interrupt did not fire within the expected time period (values in milliseconds)",
|
||||
interruptFireTime.get(),
|
||||
both(greaterThan(interruptTriggerTime - range))
|
||||
.and(lessThan(interruptTriggerTime + range)));
|
||||
assertThat(
|
||||
"The readRisingTimestamp() did not return the correct value (values in seconds)",
|
||||
interrupt.getRisingTimestamp(),
|
||||
both(greaterThan((interruptTriggerTime - range) / 1e6))
|
||||
.and(lessThan((interruptTriggerTime + range) / 1e6)));
|
||||
}
|
||||
|
||||
// Then
|
||||
assertEquals("The interrupt did not fire the expected number of times", 1, counter.getCount());
|
||||
// If the test within the interrupt failed
|
||||
if (function.m_exceptionThrown.get()) {
|
||||
throw function.m_ex;
|
||||
}
|
||||
final long range = 10000; // in microseconds
|
||||
assertThat(
|
||||
"The interrupt did not fire within the expected time period (values in milliseconds)",
|
||||
function.m_interruptFireTime.get(),
|
||||
both(greaterThan(interruptTriggerTime - range))
|
||||
.and(lessThan(interruptTriggerTime + range)));
|
||||
assertThat(
|
||||
"The readRisingTimestamp() did not return the correct value (values in seconds)",
|
||||
getInterruptable().readRisingTimestamp(),
|
||||
both(greaterThan((interruptTriggerTime - range) / 1e6))
|
||||
.and(lessThan((interruptTriggerTime + range) / 1e6)));
|
||||
}
|
||||
|
||||
@Test(timeout = 2000)
|
||||
public void testMultipleInterruptsTriggering() {
|
||||
// Given
|
||||
final InterruptCounter counter = new InterruptCounter();
|
||||
TestInterruptHandlerFunction function = new TestInterruptHandlerFunction(counter);
|
||||
|
||||
// When
|
||||
getInterruptable().requestInterrupts(function);
|
||||
getInterruptable().enableInterrupts();
|
||||
AtomicBoolean hasFired = new AtomicBoolean(false);
|
||||
AtomicInteger counter = new AtomicInteger(0);
|
||||
|
||||
final int fireCount = 50;
|
||||
for (int i = 0; i < fireCount; i++) {
|
||||
setInterruptLow();
|
||||
setInterruptHigh();
|
||||
// Wait for the interrupt to complete before moving on
|
||||
while (!function.m_interruptComplete.getAndSet(false)) {
|
||||
Timer.delay(0.005);
|
||||
try (AsynchronousInterrupt interrupt =
|
||||
new AsynchronousInterrupt(
|
||||
getSource(),
|
||||
(a, b) -> {
|
||||
hasFired.set(true);
|
||||
counter.incrementAndGet();
|
||||
})) {
|
||||
interrupt.enable();
|
||||
|
||||
final int fireCount = 50;
|
||||
for (int i = 0; i < fireCount; i++) {
|
||||
setInterruptLow();
|
||||
setInterruptHigh();
|
||||
// Wait for the interrupt to complete before moving on
|
||||
while (!hasFired.getAndSet(false)) {
|
||||
Timer.delay(0.005);
|
||||
}
|
||||
}
|
||||
// Then
|
||||
assertEquals(
|
||||
"The interrupt did not fire the expected number of times", fireCount, counter.get());
|
||||
}
|
||||
// Then
|
||||
assertEquals(
|
||||
"The interrupt did not fire the expected number of times", fireCount, counter.getCount());
|
||||
}
|
||||
|
||||
/** The timeout length for this test in seconds. */
|
||||
@@ -172,131 +130,128 @@ public abstract class AbstractInterruptTest extends AbstractComsSetup {
|
||||
|
||||
@Test(timeout = (long) (synchronousTimeout * 1e3))
|
||||
public void testSynchronousInterruptsTriggering() {
|
||||
// Given
|
||||
getInterruptable().requestInterrupts();
|
||||
|
||||
final double synchronousDelay = synchronousTimeout / 2.0;
|
||||
final Runnable runnable =
|
||||
() -> {
|
||||
Timer.delay(synchronousDelay);
|
||||
setInterruptLow();
|
||||
setInterruptHigh();
|
||||
};
|
||||
try (SynchronousInterrupt interrupt = new SynchronousInterrupt(getSource())) {
|
||||
final double synchronousDelay = synchronousTimeout / 2.0;
|
||||
final Runnable runnable =
|
||||
() -> {
|
||||
Timer.delay(synchronousDelay);
|
||||
setInterruptLow();
|
||||
setInterruptHigh();
|
||||
};
|
||||
|
||||
// When
|
||||
// When
|
||||
|
||||
// Note: the long time value is used because doubles can flip if the robot
|
||||
// is left running for long enough
|
||||
final long startTimeStamp = RobotController.getFPGATime();
|
||||
new Thread(runnable).start();
|
||||
// Delay for twice as long as the timeout so the test should fail first
|
||||
getInterruptable().waitForInterrupt(synchronousTimeout * 2);
|
||||
final long stopTimeStamp = RobotController.getFPGATime();
|
||||
// Note: the long time value is used because doubles can flip if the robot
|
||||
// is left running for long enough
|
||||
final long startTimeStamp = RobotController.getFPGATime();
|
||||
new Thread(runnable).start();
|
||||
// Delay for twice as long as the timeout so the test should fail first
|
||||
interrupt.waitForInterrupt(synchronousTimeout * 2);
|
||||
final long stopTimeStamp = RobotController.getFPGATime();
|
||||
|
||||
// Then
|
||||
// The test will not have timed out and:
|
||||
final double interruptRunTime = (stopTimeStamp - startTimeStamp) * 1e-6;
|
||||
assertEquals(
|
||||
"The interrupt did not run for the expected amount of time (units in seconds)",
|
||||
synchronousDelay,
|
||||
interruptRunTime,
|
||||
0.1);
|
||||
// Then
|
||||
// The test will not have timed out and:
|
||||
final double interruptRunTime = (stopTimeStamp - startTimeStamp) * 1e-6;
|
||||
assertEquals(
|
||||
"The interrupt did not run for the expected amount of time (units in seconds)",
|
||||
synchronousDelay,
|
||||
interruptRunTime,
|
||||
0.1);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = (long) (synchronousTimeout * 1e3))
|
||||
public void testSynchronousInterruptsWaitResultTimeout() {
|
||||
// Given
|
||||
getInterruptable().requestInterrupts();
|
||||
try (SynchronousInterrupt interrupt = new SynchronousInterrupt(getSource())) {
|
||||
SynchronousInterrupt.WaitResult result = interrupt.waitForInterrupt(synchronousTimeout / 2);
|
||||
|
||||
// Don't fire interrupt. Expect it to timeout.
|
||||
InterruptableSensorBase.WaitResult result =
|
||||
getInterruptable().waitForInterrupt(synchronousTimeout / 2);
|
||||
|
||||
assertEquals(
|
||||
"The interrupt did not time out correctly.",
|
||||
result,
|
||||
InterruptableSensorBase.WaitResult.kTimeout);
|
||||
assertEquals(
|
||||
"The interrupt did not time out correctly.",
|
||||
result,
|
||||
SynchronousInterrupt.WaitResult.kTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = (long) (synchronousTimeout * 1e3))
|
||||
public void testSynchronousInterruptsWaitResultRisingEdge() {
|
||||
// Given
|
||||
getInterruptable().requestInterrupts();
|
||||
try (SynchronousInterrupt interrupt = new SynchronousInterrupt(getSource())) {
|
||||
|
||||
final double synchronousDelay = synchronousTimeout / 2.0;
|
||||
final Runnable runnable =
|
||||
() -> {
|
||||
Timer.delay(synchronousDelay);
|
||||
setInterruptLow();
|
||||
setInterruptHigh();
|
||||
};
|
||||
final double synchronousDelay = synchronousTimeout / 2.0;
|
||||
final Runnable runnable =
|
||||
() -> {
|
||||
Timer.delay(synchronousDelay);
|
||||
setInterruptLow();
|
||||
setInterruptHigh();
|
||||
};
|
||||
|
||||
new Thread(runnable).start();
|
||||
// Delay for twice as long as the timeout so the test should fail first
|
||||
InterruptableSensorBase.WaitResult result =
|
||||
getInterruptable().waitForInterrupt(synchronousTimeout * 2);
|
||||
new Thread(runnable).start();
|
||||
// Delay for twice as long as the timeout so the test should fail first
|
||||
SynchronousInterrupt.WaitResult result = interrupt.waitForInterrupt(synchronousTimeout * 2);
|
||||
|
||||
assertEquals(
|
||||
"The interrupt did not fire on the rising edge.",
|
||||
result,
|
||||
InterruptableSensorBase.WaitResult.kRisingEdge);
|
||||
assertEquals(
|
||||
"The interrupt did not fire on the rising edge.",
|
||||
result,
|
||||
SynchronousInterrupt.WaitResult.kRisingEdge);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = (long) (synchronousTimeout * 1e3))
|
||||
public void testSynchronousInterruptsWaitResultFallingEdge() {
|
||||
// Given
|
||||
getInterruptable().requestInterrupts();
|
||||
getInterruptable().setUpSourceEdge(false, true);
|
||||
try (SynchronousInterrupt interrupt = new SynchronousInterrupt(getSource())) {
|
||||
// Given
|
||||
interrupt.setInterruptEdges(false, true);
|
||||
|
||||
final double synchronousDelay = synchronousTimeout / 2.0;
|
||||
final Runnable runnable =
|
||||
() -> {
|
||||
Timer.delay(synchronousDelay);
|
||||
setInterruptHigh();
|
||||
setInterruptLow();
|
||||
};
|
||||
final double synchronousDelay = synchronousTimeout / 2.0;
|
||||
final Runnable runnable =
|
||||
() -> {
|
||||
Timer.delay(synchronousDelay);
|
||||
setInterruptHigh();
|
||||
setInterruptLow();
|
||||
};
|
||||
|
||||
new Thread(runnable).start();
|
||||
// Delay for twice as long as the timeout so the test should fail first
|
||||
InterruptableSensorBase.WaitResult result =
|
||||
getInterruptable().waitForInterrupt(synchronousTimeout * 2);
|
||||
new Thread(runnable).start();
|
||||
// Delay for twice as long as the timeout so the test should fail first
|
||||
SynchronousInterrupt.WaitResult result = interrupt.waitForInterrupt(synchronousTimeout * 2);
|
||||
|
||||
assertEquals(
|
||||
"The interrupt did not fire on the falling edge.",
|
||||
result,
|
||||
InterruptableSensorBase.WaitResult.kFallingEdge);
|
||||
assertEquals(
|
||||
"The interrupt did not fire on the falling edge.",
|
||||
result,
|
||||
SynchronousInterrupt.WaitResult.kFallingEdge);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 4000)
|
||||
public void testDisableStopsInterruptFiring() {
|
||||
final InterruptCounter counter = new InterruptCounter();
|
||||
TestInterruptHandlerFunction function = new TestInterruptHandlerFunction(counter);
|
||||
|
||||
// When
|
||||
getInterruptable().requestInterrupts(function);
|
||||
getInterruptable().enableInterrupts();
|
||||
|
||||
final int fireCount = 50;
|
||||
for (int i = 0; i < fireCount; i++) {
|
||||
setInterruptLow();
|
||||
setInterruptHigh();
|
||||
// Wait for the interrupt to complete before moving on
|
||||
while (!function.m_interruptComplete.getAndSet(false)) {
|
||||
AtomicBoolean interruptComplete = new AtomicBoolean(false);
|
||||
AtomicInteger counter = new AtomicInteger(0);
|
||||
try (AsynchronousInterrupt interrupt =
|
||||
new AsynchronousInterrupt(
|
||||
getSource(),
|
||||
(a, b) -> {
|
||||
interruptComplete.set(true);
|
||||
counter.incrementAndGet();
|
||||
})) {
|
||||
interrupt.enable();
|
||||
final int fireCount = 50;
|
||||
for (int i = 0; i < fireCount; i++) {
|
||||
setInterruptLow();
|
||||
setInterruptHigh();
|
||||
// Wait for the interrupt to complete before moving on
|
||||
while (!interruptComplete.getAndSet(false)) {
|
||||
Timer.delay(0.005);
|
||||
}
|
||||
}
|
||||
interrupt.disable();
|
||||
for (int i = 0; i < fireCount; i++) {
|
||||
setInterruptLow();
|
||||
setInterruptHigh();
|
||||
// Just wait because the interrupt should not fire
|
||||
Timer.delay(0.005);
|
||||
}
|
||||
}
|
||||
getInterruptable().disableInterrupts();
|
||||
// TestBench.out().println("Finished disabling the robot");
|
||||
|
||||
for (int i = 0; i < fireCount; i++) {
|
||||
setInterruptLow();
|
||||
setInterruptHigh();
|
||||
// Just wait because the interrupt should not fire
|
||||
Timer.delay(0.005);
|
||||
assertEquals(
|
||||
"The interrupt did not fire the expected number of times", fireCount, counter.get());
|
||||
}
|
||||
|
||||
// Then
|
||||
assertEquals(
|
||||
"The interrupt did not fire the expected number of times", fireCount, counter.getCount());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,10 +139,10 @@ public class AnalogCrossConnectTest extends AbstractInterruptTest {
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* edu.wpi.first.wpilibj.AbstractInterruptTest#giveInterruptableSensorBase()
|
||||
* edu.wpi.first.wpilibj.AbstractInterruptTest#giveSource()
|
||||
*/
|
||||
@Override
|
||||
InterruptableSensorBase giveInterruptableSensorBase() {
|
||||
DigitalSource giveSource() {
|
||||
m_interruptTrigger = new AnalogTrigger(analogIO.getInput());
|
||||
m_interruptTrigger.setLimitsVoltage(2.0, 3.0);
|
||||
m_interruptTriggerOutput =
|
||||
@@ -154,11 +154,10 @@ public class AnalogCrossConnectTest extends AbstractInterruptTest {
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* edu.wpi.first.wpilibj.AbstractInterruptTest#freeInterruptableSensorBase()
|
||||
* edu.wpi.first.wpilibj.AbstractInterruptTest#freeSource()
|
||||
*/
|
||||
@Override
|
||||
void freeInterruptableSensorBase() {
|
||||
m_interruptTriggerOutput.cancelInterrupts();
|
||||
void freeSource() {
|
||||
m_interruptTriggerOutput.close();
|
||||
m_interruptTriggerOutput = null;
|
||||
m_interruptTrigger.close();
|
||||
|
||||
@@ -101,10 +101,11 @@ public class DIOCrossConnectTest extends AbstractInterruptTest {
|
||||
dio.getOutput().enablePWM(0.0);
|
||||
Timer.delay(0.5);
|
||||
dio.getOutput().updateDutyCycle(0.5);
|
||||
dio.getInput().requestInterrupts();
|
||||
dio.getInput().setUpSourceEdge(false, true);
|
||||
// TODO: Add return value from WaitForInterrupt
|
||||
dio.getInput().waitForInterrupt(3.0, true);
|
||||
try (var interruptHandler = new SynchronousInterrupt(dio.getInput())) {
|
||||
interruptHandler.setInterruptEdges(false, true);
|
||||
// TODO: Add return value from WaitForInterrupt
|
||||
interruptHandler.waitForInterrupt(3.0, true);
|
||||
}
|
||||
Timer.delay(0.5);
|
||||
final boolean firstCycle = dio.getInput().get();
|
||||
Timer.delay(0.5);
|
||||
@@ -140,10 +141,10 @@ public class DIOCrossConnectTest extends AbstractInterruptTest {
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* edu.wpi.first.wpilibj.AbstractInterruptTest#giveInterruptableSensorBase()
|
||||
* edu.wpi.first.wpilibj.AbstractInterruptTest#giveSource()
|
||||
*/
|
||||
@Override
|
||||
InterruptableSensorBase giveInterruptableSensorBase() {
|
||||
DigitalSource giveSource() {
|
||||
return dio.getInput();
|
||||
}
|
||||
|
||||
@@ -151,10 +152,10 @@ public class DIOCrossConnectTest extends AbstractInterruptTest {
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* edu.wpi.first.wpilibj.AbstractInterruptTest#freeInterruptableSensorBase()
|
||||
* edu.wpi.first.wpilibj.AbstractInterruptTest#freeSource()
|
||||
*/
|
||||
@Override
|
||||
void freeInterruptableSensorBase() {
|
||||
void freeSource() {
|
||||
// Handled in the fixture
|
||||
}
|
||||
|
||||
|
||||
@@ -61,11 +61,6 @@ public class DIOCrossConnectFixture implements ITestFixture {
|
||||
|
||||
@Override
|
||||
public boolean reset() {
|
||||
try {
|
||||
m_input.cancelInterrupts();
|
||||
} catch (IllegalStateException ex) {
|
||||
// This will happen if the interrupt has not been allocated for this test.
|
||||
}
|
||||
m_output.set(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user