mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-20 00:51:42 +00:00
Adds interrupts to Java
Implements the JNI bindings for java Adds integration tests for Digital Inputs and AnalogTriggers. Adds the ability to get the value and message from errno in java using the HALUtil JNI class. Change-Id: I853529fdab9744ce95ee15d4cc73dc3953265552
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2008-2014. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
package edu.wpi.first.wpilibj;
|
||||
|
||||
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 java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import edu.wpi.first.wpilibj.test.AbstractComsSetup;
|
||||
|
||||
/**
|
||||
* This class should not be run as a test explicitly. Instead it should be extended by tests that use the InterruptableSensorBase
|
||||
*
|
||||
* @author jonathanleitschuh
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractInterruptTest extends AbstractComsSetup {
|
||||
private InterruptableSensorBase interruptable = null;
|
||||
|
||||
private InterruptableSensorBase getInterruptable(){
|
||||
if(interruptable == null){
|
||||
interruptable = giveInterruptableSensorBase();
|
||||
}
|
||||
return interruptable;
|
||||
}
|
||||
|
||||
|
||||
@After
|
||||
public void interruptTeardown(){
|
||||
if(interruptable != null){
|
||||
freeInterruptableSensorBase();
|
||||
interruptable = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Give the interruptible sensor base that interrupts can be attached to.
|
||||
* @return
|
||||
*/
|
||||
abstract InterruptableSensorBase giveInterruptableSensorBase();
|
||||
/**
|
||||
* Cleans up the interruptible sensor base. This is only called if {@link #giveInterruptableSensorBase()} is called.
|
||||
*/
|
||||
abstract void freeInterruptableSensorBase();
|
||||
/**
|
||||
* Perform whatever action is required to set the interrupt high.
|
||||
*/
|
||||
abstract void setInterruptHigh();
|
||||
/**
|
||||
* Perform whatever action is required to set the interrupt low.
|
||||
*/
|
||||
abstract void setInterruptLow();
|
||||
|
||||
|
||||
private class InterruptCounter{
|
||||
private final AtomicInteger count = new AtomicInteger();
|
||||
void increment(){
|
||||
count.addAndGet(1);
|
||||
}
|
||||
|
||||
int getCount(){
|
||||
return count.get();
|
||||
}
|
||||
};
|
||||
|
||||
private class TestInterruptHandlerFunction extends InterruptHandlerFunction<InterruptCounter>{
|
||||
protected final AtomicBoolean exceptionThrown = new AtomicBoolean(false);
|
||||
/** Stores the time that the interrupt fires */
|
||||
final AtomicLong interruptFireTime = new AtomicLong();
|
||||
/** Stores if the interrupt has completed at least once */
|
||||
final AtomicBoolean interruptComplete = new AtomicBoolean(false);
|
||||
protected Exception ex;
|
||||
final InterruptCounter counter;
|
||||
|
||||
TestInterruptHandlerFunction(InterruptCounter counter){
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
@Override
|
||||
void interruptFired(int interruptAssertedMask, InterruptCounter param) {
|
||||
interruptFireTime.set(Utility.getFPGATime());
|
||||
counter.increment();
|
||||
try{
|
||||
//This won't cause the test to fail
|
||||
assertSame(counter, param);
|
||||
} catch (Exception ex){
|
||||
//So we must throw the exception within the test
|
||||
exceptionThrown.set(true);
|
||||
this.ex = ex;
|
||||
}
|
||||
interruptComplete.set(true);
|
||||
};
|
||||
|
||||
@Override
|
||||
public InterruptCounter overridableParamater(){
|
||||
return counter;
|
||||
}
|
||||
};
|
||||
|
||||
@Test(timeout = 1000)
|
||||
public void testSingleInterruptsTriggering() throws Exception{
|
||||
//Given
|
||||
final InterruptCounter counter = new InterruptCounter();
|
||||
TestInterruptHandlerFunction function = new TestInterruptHandlerFunction(counter);
|
||||
|
||||
//When
|
||||
getInterruptable().requestInterrupts(function);
|
||||
getInterruptable().enableInterrupts();
|
||||
|
||||
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 = Utility.getFPGATime();
|
||||
setInterruptHigh();
|
||||
|
||||
//Delay until the interrupt is complete
|
||||
while(!function.interruptComplete.get()){
|
||||
Timer.delay(.005);
|
||||
}
|
||||
|
||||
|
||||
//Then
|
||||
assertEquals("The interrupt did not fire the expected number of times", 1, counter.getCount());
|
||||
//If the test within the interrupt failed
|
||||
if(function.exceptionThrown.get()){
|
||||
throw function.ex;
|
||||
}
|
||||
final long range = 10000; //in microseconds
|
||||
assertThat("The interrupt did not fire within the expected time period (values in milliseconds)",
|
||||
function.interruptFireTime.get(), both(greaterThan(interruptTriggerTime - range))
|
||||
.and(lessThan(interruptTriggerTime + range)));
|
||||
assertThat("The readInterruptTimestamp() did not return the correct value (values in seconds)",
|
||||
getInterruptable().readInterruptTimestamp(), both(greaterThan((interruptTriggerTime - range)/1e6))
|
||||
.and(lessThan((interruptTriggerTime + range)/1e6)));
|
||||
}
|
||||
|
||||
@Test(timeout = 1000)
|
||||
public void testMultipleInterruptsTriggering() throws Exception{
|
||||
//Given
|
||||
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.interruptComplete.getAndSet(false)){
|
||||
Timer.delay(.005);
|
||||
}
|
||||
}
|
||||
//Then
|
||||
assertEquals("The interrupt did not fire the expected number of times", fireCount, counter.getCount());
|
||||
}
|
||||
|
||||
/** The timeout length for this test in seconds */
|
||||
private static final int synchronousTimeout = 5;
|
||||
@Test(timeout = (long)(synchronousTimeout*1e3))
|
||||
public void testSynchronousInterruptsTriggering(){
|
||||
//Given
|
||||
getInterruptable().requestInterrupts();
|
||||
|
||||
final double synchronousDelay = synchronousTimeout/2.;
|
||||
Runnable r = new Runnable(){
|
||||
@Override
|
||||
public void run() {
|
||||
Timer.delay(synchronousDelay);
|
||||
setInterruptLow();
|
||||
setInterruptHigh();
|
||||
}
|
||||
};
|
||||
|
||||
//When
|
||||
|
||||
//Note: the long time value is used because doubles can flip if the robot is left running for long enough
|
||||
final long startTimeStamp = Utility.getFPGATime();
|
||||
new Thread(r).start();
|
||||
//Delay for twice as long as the timeout so the test should fail first
|
||||
getInterruptable().waitForInterrupt(synchronousTimeout * 2);
|
||||
final long stopTimeStamp = Utility.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, .1);
|
||||
}
|
||||
|
||||
|
||||
@Test(timeout = 2000)
|
||||
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.interruptComplete.getAndSet(false)){
|
||||
Timer.delay(.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(.005);
|
||||
}
|
||||
|
||||
//Then
|
||||
assertEquals("The interrupt did not fire the expected number of times", fireCount, counter.getCount());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,7 +6,9 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
package edu.wpi.first.wpilibj;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -16,15 +18,15 @@ import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import edu.wpi.first.wpilibj.AnalogTriggerOutput.AnalogTriggerType;
|
||||
import edu.wpi.first.wpilibj.fixtures.AnalogCrossConnectFixture;
|
||||
import edu.wpi.first.wpilibj.test.AbstractComsSetup;
|
||||
import edu.wpi.first.wpilibj.test.TestBench;
|
||||
|
||||
/**
|
||||
* @author jonathanleitschuh
|
||||
*
|
||||
*/
|
||||
public class AnalogCrossConnectTest extends AbstractComsSetup {
|
||||
public class AnalogCrossConnectTest extends AbstractInterruptTest {
|
||||
private static final Logger logger = Logger.getLogger(AnalogCrossConnectTest.class.getName());
|
||||
|
||||
private static AnalogCrossConnectFixture analogIO;
|
||||
@@ -133,7 +135,6 @@ public class AnalogCrossConnectTest extends AbstractComsSetup {
|
||||
// Given
|
||||
AnalogTrigger trigger = new AnalogTrigger(analogIO.getInput());
|
||||
trigger.setLimitsVoltage(2.0f, 3.0f);
|
||||
|
||||
Counter counter = new Counter(trigger);
|
||||
|
||||
// When the analog output is turned low and high 50 times
|
||||
@@ -152,4 +153,48 @@ public class AnalogCrossConnectTest extends AbstractComsSetup {
|
||||
public void testRuntimeExceptionOnInvalidAccumulatorPort(){
|
||||
analogIO.getInput().getAccumulatorCount();
|
||||
}
|
||||
|
||||
private AnalogTrigger interruptTrigger;
|
||||
private AnalogTriggerOutput interruptTriggerOutput;
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see edu.wpi.first.wpilibj.AbstractInterruptTest#giveInterruptableSensorBase()
|
||||
*/
|
||||
@Override
|
||||
InterruptableSensorBase giveInterruptableSensorBase() {
|
||||
interruptTrigger = new AnalogTrigger(analogIO.getInput());
|
||||
interruptTrigger.setLimitsVoltage(2.0f, 3.0f);
|
||||
interruptTriggerOutput = new AnalogTriggerOutput(interruptTrigger, AnalogTriggerType.STATE);
|
||||
return interruptTriggerOutput;
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see edu.wpi.first.wpilibj.AbstractInterruptTest#freeInterruptableSensorBase()
|
||||
*/
|
||||
@Override
|
||||
void freeInterruptableSensorBase() {
|
||||
interruptTriggerOutput.cancelInterrupts();
|
||||
interruptTriggerOutput.free();
|
||||
interruptTriggerOutput = null;
|
||||
interruptTrigger.free();
|
||||
interruptTrigger = null;
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see edu.wpi.first.wpilibj.AbstractInterruptTest#setInterruptHigh()
|
||||
*/
|
||||
@Override
|
||||
void setInterruptHigh() {
|
||||
analogIO.getOutput().setVoltage(4.0);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see edu.wpi.first.wpilibj.AbstractInterruptTest#setInterruptLow()
|
||||
*/
|
||||
@Override
|
||||
void setInterruptLow() {
|
||||
analogIO.getOutput().setVoltage(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,12 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
package edu.wpi.first.wpilibj;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import edu.wpi.first.wpilibj.fixtures.AnalogCrossConnectFixture;
|
||||
@@ -68,8 +66,8 @@ public class AnalogPotentiometerTest extends AbstractComsSetup {
|
||||
public void testRangeValues(){
|
||||
for(double i = 0.0; i < 360.0; i = i+1.0){
|
||||
potSource.setAngle(i);
|
||||
assertEquals(i, pot.get(), DOUBLE_COMPARISON_DELTA);
|
||||
Timer.delay(.02);
|
||||
assertEquals(i, pot.get(), DOUBLE_COMPARISON_DELTA);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,16 +14,12 @@ import java.util.logging.Logger;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
import edu.wpi.first.wpilibj.fixtures.DIOCrossConnectFixture;
|
||||
import edu.wpi.first.wpilibj.test.AbstractComsSetup;
|
||||
import edu.wpi.first.wpilibj.test.TestBench;
|
||||
|
||||
/**
|
||||
@@ -31,11 +27,12 @@ import edu.wpi.first.wpilibj.test.TestBench;
|
||||
* @author jonathanleitschuh
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class DIOCrossConnectTest extends AbstractComsSetup {
|
||||
public class DIOCrossConnectTest extends AbstractInterruptTest {
|
||||
private static final Logger logger = Logger.getLogger(DIOCrossConnectTest.class.getName());
|
||||
|
||||
private static DIOCrossConnectFixture dio = null;
|
||||
|
||||
@Override
|
||||
protected Logger getClassLogger(){
|
||||
return logger;
|
||||
}
|
||||
@@ -65,7 +62,7 @@ public class DIOCrossConnectTest extends AbstractComsSetup {
|
||||
* Array in the Collection, each array element corresponds to a parameter
|
||||
* in the constructor.
|
||||
*/
|
||||
@Parameters
|
||||
@Parameters(name= "{index}: Input Port: {0} Output Port: {1}")
|
||||
public static Collection<Integer[]> generateData() {
|
||||
// In this example, the parameter generator returns a List of
|
||||
// arrays. Each array has two elements: { Digital input port, Digital output port}.
|
||||
@@ -74,24 +71,14 @@ public class DIOCrossConnectTest extends AbstractComsSetup {
|
||||
return TestBench.getInstance().getDIOCrossConnectCollection();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpBeforeClass() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDownAfterClass() throws Exception {
|
||||
dio.teardown();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
dio.reset();
|
||||
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
dio.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,4 +102,38 @@ public class DIOCrossConnectTest extends AbstractComsSetup {
|
||||
Timer.delay(.02);
|
||||
assertFalse("DIO Not Low after .05s delay", dio.getInput().get());
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see edu.wpi.first.wpilibj.AbstractInterruptTest#giveInterruptableSensorBase()
|
||||
*/
|
||||
@Override
|
||||
InterruptableSensorBase giveInterruptableSensorBase() {
|
||||
return dio.getInput();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see edu.wpi.first.wpilibj.AbstractInterruptTest#freeInterruptableSensorBase()
|
||||
*/
|
||||
@Override
|
||||
void freeInterruptableSensorBase() {
|
||||
// Handled in the fixture
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see edu.wpi.first.wpilibj.AbstractInterruptTest#setInterruptHigh()
|
||||
*/
|
||||
@Override
|
||||
void setInterruptHigh() {
|
||||
dio.getOutput().set(true);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see edu.wpi.first.wpilibj.AbstractInterruptTest#setInterruptLow()
|
||||
*/
|
||||
@Override
|
||||
void setInterruptLow() {
|
||||
dio.getOutput().set(false);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -52,6 +52,11 @@ public class DIOCrossConnectFixture implements ITestFixture {
|
||||
|
||||
@Override
|
||||
public boolean reset() {
|
||||
try{
|
||||
input.cancelInterrupts();
|
||||
} catch(IllegalStateException e) {
|
||||
//This will happen if the interrupt has not been allocated for this test.
|
||||
}
|
||||
output.set(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user