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:
Jonathan Leitschuh
2014-08-04 14:19:01 -04:00
parent 23b6a980c2
commit 8ba0eada17
18 changed files with 834 additions and 213 deletions

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}