From 8ba0eada17afd93b2a4c01f08cc03538ff566d1d Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Mon, 4 Aug 2014 14:19:01 -0400 Subject: [PATCH] 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 --- hal/include/HAL/Interrupts.hpp | 3 + hal/lib/Athena/Interrupts.cpp | 2 + .../libRoboRIO_FRC_ChipObject.so.1.2.0 | Bin 376888 -> 376888 bytes .../first/wpilibj/AnalogTriggerOutput.java | 26 +- .../edu/wpi/first/wpilibj/DigitalInput.java | 95 +------ .../edu/wpi/first/wpilibj/DigitalOutput.java | 22 +- .../edu/wpi/first/wpilibj/DigitalSource.java | 10 +- .../wpilibj/InterruptHandlerFunction.java | 57 +++++ .../wpilibj/InterruptableSensorBase.java | 210 ++++++++++++--- .../edu/wpi/first/wpilibj/hal/HALUtil.java | 8 +- .../wpi/first/wpilibj/hal/InterruptJNI.java | 9 +- .../first/wpilibj/AbstractInterruptTest.java | 239 ++++++++++++++++++ .../first/wpilibj/AnalogCrossConnectTest.java | 53 +++- .../wpilibj/AnalogPotentiometerTest.java | 6 +- .../first/wpilibj/DIOCrossConnectTest.java | 55 ++-- .../fixtures/DIOCrossConnectFixture.java | 5 + wpilibj/wpilibJavaJNI/lib/HALUtil.cpp | 26 ++ wpilibj/wpilibJavaJNI/lib/InterruptJNI.cpp | 221 ++++++++++++++-- 18 files changed, 834 insertions(+), 213 deletions(-) create mode 100644 wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/InterruptHandlerFunction.java create mode 100644 wpilibj/wpilibJavaIntegrationTests/src/main/java/edu/wpi/first/wpilibj/AbstractInterruptTest.java diff --git a/hal/include/HAL/Interrupts.hpp b/hal/include/HAL/Interrupts.hpp index 4406cc1565..f5614f5843 100644 --- a/hal/include/HAL/Interrupts.hpp +++ b/hal/include/HAL/Interrupts.hpp @@ -6,6 +6,9 @@ #include #endif +#include +#include "errno.h" + extern "C" { typedef void (*InterruptHandlerFunction)(uint32_t interruptAssertedMask, void *param); diff --git a/hal/lib/Athena/Interrupts.cpp b/hal/lib/Athena/Interrupts.cpp index 941118d4d6..aad7b08647 100644 --- a/hal/lib/Athena/Interrupts.cpp +++ b/hal/lib/Athena/Interrupts.cpp @@ -52,8 +52,10 @@ void enableInterrupts(void* interrupt_pointer, int32_t *status) */ void disableInterrupts(void* interrupt_pointer, int32_t *status) { + Interrupt* anInterrupt = (Interrupt*)interrupt_pointer; anInterrupt->manager->disable(status); + } /** diff --git a/ni-libraries/libRoboRIO_FRC_ChipObject.so.1.2.0 b/ni-libraries/libRoboRIO_FRC_ChipObject.so.1.2.0 index 6b511629e939356992f50ddf44fbcce154ee59f8..1891aba6c5a88e8a66e698a5ed64deaf1e1080f2 100755 GIT binary patch delta 272 zcmdndAikqPyrG4$g=q_OKd>`=?^ga?#=zS!xp!X4j!-9tl4GSJK1x$X< z#=!83fg#|rAOpiL28Q6rLJSOCf(%!lg2W_&Y#xCrPk-4jc+8?O1t>Q4sX#;TQ-*@x zrz{D*Pp?k+|5`v`?o)AtyFj_X$M63C-}U?TX^giNg=zYZCu;3v>z=Fq236r05GBCVi zUFsB=gO)8r0iW;%{7e}^87r*Qj3Z+^Yh}J w^K)}k^Gb?sjSLJ;^bO4QjVu%l&8!T~t&9z~&v#`_bYR}FhIu=48>^`m03IW5YXATM diff --git a/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/AnalogTriggerOutput.java b/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/AnalogTriggerOutput.java index 6c6dcaea8f..b79c1820f5 100644 --- a/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/AnalogTriggerOutput.java +++ b/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/AnalogTriggerOutput.java @@ -92,6 +92,7 @@ public class AnalogTriggerOutput extends DigitalSource { @Override public void free() { + } /** @@ -113,8 +114,8 @@ public class AnalogTriggerOutput extends DigitalSource { } @Override - public int getModuleForRouting() { - return m_trigger.m_index >> 2; + public byte getModuleForRouting() { + return (byte) (m_trigger.m_index >> 2); } @Override @@ -122,27 +123,6 @@ public class AnalogTriggerOutput extends DigitalSource { return true; } - /** - * Request interrupts asynchronously on this digital input. - * - * @param handler - * the interrupt service routine - * @param param - * a parameter for the ISR - */ - // public void requestInterrupts(/*tInterruptHandler*/Object handler, Object - // param) { - // TODO: add interrupt support - // TODO: throw exception - // } - - /** - * Request interrupts synchronously on this digital input. - */ - // public void requestInterrupts() { - // TODO: throw exception - // } - /** * Defines the state in which the AnalogTrigger triggers * @author jonathanleitschuh diff --git a/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/DigitalInput.java b/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/DigitalInput.java index 1a2d8ebd7f..f1f610e285 100644 --- a/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/DigitalInput.java +++ b/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/DigitalInput.java @@ -12,14 +12,10 @@ import java.nio.ByteOrder; import edu.wpi.first.wpilibj.communication.FRCNetworkCommunicationsLibrary.tResourceType; import edu.wpi.first.wpilibj.communication.UsageReporting; -import edu.wpi.first.wpilibj.hal.InterruptJNI; import edu.wpi.first.wpilibj.hal.DIOJNI; -import edu.wpi.first.wpilibj.hal.InterruptJNI.InterruptHandlerFunction; import edu.wpi.first.wpilibj.hal.HALUtil; import edu.wpi.first.wpilibj.livewindow.LiveWindowSendable; import edu.wpi.first.wpilibj.tables.ITable; -import edu.wpi.first.wpilibj.util.AllocationException; -import edu.wpi.first.wpilibj.util.CheckedAllocationException; /** * Class to read a digital input. This class will read digital inputs and return @@ -67,97 +63,15 @@ public class DigitalInput extends DigitalSource implements LiveWindowSendable { return m_channel; } + @Override public boolean getAnalogTriggerForRouting() { return false; } - /** - * Request interrupts asynchronously on this digital input. - * - * @param handler - * The address of the interrupt handler function of type - * tInterruptHandler that will be called whenever there is an - * interrupt on the digitial input port. Request interrupts in - * synchronus 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) { - // TODO: add interrupt support - - try { - m_interruptIndex = interrupts.allocate(); - } catch (CheckedAllocationException e) { - throw new AllocationException( - "No interrupts are left to be allocated"); - } - - allocateInterrupts(false); - - ByteBuffer status = ByteBuffer.allocateDirect(4); - // set the byte order - status.order(ByteOrder.LITTLE_ENDIAN); - InterruptJNI.requestInterrupts(m_interrupt, (byte) getModuleForRouting(), - getChannelForRouting(), - (byte) (getAnalogTriggerForRouting() ? 1 : 0), status.asIntBuffer()); - setUpSourceEdge(true, false); - InterruptJNI.attachInterruptHandler(m_interrupt, handler, null, status.asIntBuffer()); - HALUtil.checkStatus(status.asIntBuffer()); - } - - /** - * Request interrupts synchronously on this digital input. Request - * interrupts in synchronus mode where the user program will have to - * explicitly wait for the interrupt to occur. The default is interrupt on - * rising edges only. - */ - public void requestInterrupts() { - try { - m_interruptIndex = interrupts.allocate(); - } catch (CheckedAllocationException e) { - throw new AllocationException( - "No interrupts are left to be allocated"); - } - - allocateInterrupts(true); - - ByteBuffer status = ByteBuffer.allocateDirect(4); - // set the byte order - status.order(ByteOrder.LITTLE_ENDIAN); - InterruptJNI.requestInterrupts(m_interrupt, (byte) getModuleForRouting(), - getChannelForRouting(), - (byte) (getAnalogTriggerForRouting() ? 1 : 0), status.asIntBuffer()); - HALUtil.checkStatus(status.asIntBuffer()); - setUpSourceEdge(true, false); - - } - - /** - * 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 != null) { - ByteBuffer status = ByteBuffer.allocateDirect(4); - // set the byte order - status.order(ByteOrder.LITTLE_ENDIAN); - InterruptJNI.setInterruptUpSourceEdge(m_interrupt, - (byte) (risingEdge ? 1 : 0), (byte) (fallingEdge ? 1 : 0), - status.asIntBuffer()); - HALUtil.checkStatus(status.asIntBuffer()); - } else { - throw new IllegalArgumentException( - "You must call RequestInterrupts before setUpSourceEdge"); - } - } - /* * Live Window code, only does anything if live window is activated. */ + @Override public String getSmartDashboardType() { return "Digital Input"; } @@ -167,6 +81,7 @@ public class DigitalInput extends DigitalSource implements LiveWindowSendable { /** * {@inheritDoc} */ + @Override public void initTable(ITable subtable) { m_table = subtable; updateTable(); @@ -175,6 +90,7 @@ public class DigitalInput extends DigitalSource implements LiveWindowSendable { /** * {@inheritDoc} */ + @Override public void updateTable() { if (m_table != null) { m_table.putBoolean("Value", get()); @@ -184,6 +100,7 @@ public class DigitalInput extends DigitalSource implements LiveWindowSendable { /** * {@inheritDoc} */ + @Override public ITable getTable() { return m_table; } @@ -191,12 +108,14 @@ public class DigitalInput extends DigitalSource implements LiveWindowSendable { /** * {@inheritDoc} */ + @Override public void startLiveWindowMode() { } /** * {@inheritDoc} */ + @Override public void stopLiveWindowMode() { } } diff --git a/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/DigitalOutput.java b/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/DigitalOutput.java index 7365648da9..0d58088162 100644 --- a/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/DigitalOutput.java +++ b/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/DigitalOutput.java @@ -7,24 +7,21 @@ package edu.wpi.first.wpilibj; -import java.nio.ByteOrder; -import java.nio.IntBuffer; import java.nio.ByteBuffer; +import java.nio.ByteOrder; -//import com.sun.jna.Pointer; - - -import edu.wpi.first.wpilibj.communication.FRCNetworkCommunicationsLibrary.tResourceType; import edu.wpi.first.wpilibj.communication.UsageReporting; import edu.wpi.first.wpilibj.hal.DIOJNI; -import edu.wpi.first.wpilibj.hal.PWMJNI; import edu.wpi.first.wpilibj.hal.HALUtil; +import edu.wpi.first.wpilibj.hal.PWMJNI; import edu.wpi.first.wpilibj.livewindow.LiveWindowSendable; import edu.wpi.first.wpilibj.tables.ITable; import edu.wpi.first.wpilibj.tables.ITableListener; +//import com.sun.jna.Pointer; +import edu.wpi.first.wpilibj.communication.FRCNetworkCommunicationsLibrary.tResourceType; /** - * Class to write digital outputs. This class will wrtie digital outputs. Other + * Class to write digital outputs. This class will write digital outputs. Other * devices that are implemented elsewhere will automatically allocate digital * inputs and outputs as required. */ @@ -48,6 +45,7 @@ public class DigitalOutput extends DigitalSource implements LiveWindowSendable { /** * Free the resources associated with a digital output. */ + @Override public void free() { // disable the pwm only if we have allocated it if (m_pwmGenerator != null) { @@ -104,6 +102,7 @@ public class DigitalOutput extends DigitalSource implements LiveWindowSendable { * @param pulseLength * The length of the pulse. */ + @Deprecated public void pulse(final int channel, final int pulseLength) { ByteBuffer status = ByteBuffer.allocateDirect(4); // set the byte order @@ -212,6 +211,7 @@ public class DigitalOutput extends DigitalSource implements LiveWindowSendable { /* * Live Window code, only does anything if live window is activated. */ + @Override public String getSmartDashboardType() { return "Digital Output"; } @@ -222,6 +222,7 @@ public class DigitalOutput extends DigitalSource implements LiveWindowSendable { /** * {@inheritDoc} */ + @Override public void initTable(ITable subtable) { m_table = subtable; updateTable(); @@ -230,6 +231,7 @@ public class DigitalOutput extends DigitalSource implements LiveWindowSendable { /** * {@inheritDoc} */ + @Override public ITable getTable() { return m_table; } @@ -237,6 +239,7 @@ public class DigitalOutput extends DigitalSource implements LiveWindowSendable { /** * {@inheritDoc} */ + @Override public void updateTable() { // TODO: Put current value. } @@ -244,8 +247,10 @@ public class DigitalOutput extends DigitalSource implements LiveWindowSendable { /** * {@inheritDoc} */ + @Override public void startLiveWindowMode() { m_table_listener = new ITableListener() { + @Override public void valueChanged(ITable itable, String key, Object value, boolean bln) { set(((Boolean) value).booleanValue()); @@ -257,6 +262,7 @@ public class DigitalOutput extends DigitalSource implements LiveWindowSendable { /** * {@inheritDoc} */ + @Override public void stopLiveWindowMode() { // TODO: Broken, should only remove the listener from "Value" only. m_table.removeTableListener(m_table_listener); diff --git a/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/DigitalSource.java b/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/DigitalSource.java index 9fda2678fa..11b2760ed2 100644 --- a/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/DigitalSource.java +++ b/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/DigitalSource.java @@ -7,9 +7,8 @@ package edu.wpi.first.wpilibj; -import java.nio.ByteOrder; -import java.nio.IntBuffer; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import edu.wpi.first.wpilibj.hal.DIOJNI; import edu.wpi.first.wpilibj.hal.HALUtil; @@ -54,6 +53,7 @@ public abstract class DigitalSource extends InterruptableSensorBase { HALUtil.checkStatus(status.asIntBuffer()); } + @Override public void free() { channels.free(m_channel); ByteBuffer status = ByteBuffer.allocateDirect(4); @@ -69,6 +69,7 @@ public abstract class DigitalSource extends InterruptableSensorBase { * * @return channel routing number */ + @Override public int getChannelForRouting() { return m_channel; } @@ -78,15 +79,16 @@ public abstract class DigitalSource extends InterruptableSensorBase { * * @return 0 */ - public int getModuleForRouting() { + @Override + public byte getModuleForRouting() { return 0; } /** * Is this an analog trigger - * * @return true if this is an analog trigger */ + @Override public boolean getAnalogTriggerForRouting() { return false; } diff --git a/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/InterruptHandlerFunction.java b/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/InterruptHandlerFunction.java new file mode 100644 index 0000000000..747c1e8f5f --- /dev/null +++ b/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/InterruptHandlerFunction.java @@ -0,0 +1,57 @@ +/*----------------------------------------------------------------------------*/ +/* 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 edu.wpi.first.wpilibj.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. + * + * @author Jonathan Leitschuh + * + * @param The type of the parameter that should be returned to the the + * method {@link #interruptFired(int, Object)} + */ +public abstract class InterruptHandlerFunction{ + /** + * 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. + * @author Jonathan Leitschuh + */ + private class Function implements InterruptJNIHandlerFunction{ + @SuppressWarnings("unchecked") + @Override + public void apply(int interruptAssertedMask, Object param) { + interruptFired(interruptAssertedMask, (T)param); + } + } + final Function function = new Function(); + + /** + * This method is run every time an interrupt is fired. + * @param interruptAssertedMask + * @param param The parameter provided by overriding the {@link #overridableParamater()} + * method. + */ + 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 overridableParamater(){ + return null; + } +} \ No newline at end of file diff --git a/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/InterruptableSensorBase.java b/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/InterruptableSensorBase.java index f38b2c06b5..d92c28afb0 100644 --- a/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/InterruptableSensorBase.java +++ b/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/InterruptableSensorBase.java @@ -7,26 +7,40 @@ package edu.wpi.first.wpilibj; -import java.nio.IntBuffer; import java.nio.ByteBuffer; +import java.nio.ByteOrder; -import edu.wpi.first.wpilibj.hal.InterruptJNI; -import edu.wpi.first.wpilibj.hal.InterruptJNI.InterruptHandlerFunction; import edu.wpi.first.wpilibj.hal.HALUtil; +import edu.wpi.first.wpilibj.hal.InterruptJNI; +import edu.wpi.first.wpilibj.util.AllocationException; +import edu.wpi.first.wpilibj.util.CheckedAllocationException; /** * Base for sensors to be used with interrupts */ public abstract class InterruptableSensorBase extends SensorBase { + /** + * This is done to store the JVM variable in the InterruptJNI + * This is done because the HAL must have access to the JVM variable + * in order to attach the newly spawned thread when an interrupt is fired. + */ + static{ + ByteBuffer status = ByteBuffer.allocateDirect(4); + status.order(ByteOrder.LITTLE_ENDIAN); + InterruptJNI.initializeInterruptJVM(status.asIntBuffer()); + HALUtil.checkStatus(status.asIntBuffer()); + } /** * The interrupt resource */ - protected ByteBuffer m_interrupt; + protected ByteBuffer m_interrupt = null; + /** - * The interrupt manager resource + * Flags if the interrupt being allocated is synchronous */ - protected InterruptHandlerFunction m_manager; + protected boolean m_isSynchronousInterrupt = false; + /** * The index of the interrupt */ @@ -40,25 +54,102 @@ public abstract class InterruptableSensorBase extends SensorBase { * Create a new InterrupatableSensorBase */ public InterruptableSensorBase() { - m_manager = null; m_interrupt = null; } + + /** + * @return + */ + abstract boolean getAnalogTriggerForRouting(); + + /** + * @return + */ + abstract int getChannelForRouting(); + + /** + * @return + */ + abstract byte getModuleForRouting(); + + /** + * Request 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 synchronus 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 != null){ + throw new AllocationException("The interrupt has already been allocated"); + } + + allocateInterrupts(false); + + assert (m_interrupt != null); + + ByteBuffer status = ByteBuffer.allocateDirect(4); + // set the byte order + status.order(ByteOrder.LITTLE_ENDIAN); + InterruptJNI.requestInterrupts(m_interrupt, getModuleForRouting(), + getChannelForRouting(), + (byte) (getAnalogTriggerForRouting() ? 1 : 0), status.asIntBuffer()); + HALUtil.checkStatus(status.asIntBuffer()); + setUpSourceEdge(true, false); + InterruptJNI.attachInterruptHandler(m_interrupt, handler.function, handler.overridableParamater(), status.asIntBuffer()); + HALUtil.checkStatus(status.asIntBuffer()); + } + + /** + * Request 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. The default is interrupt on + * rising edges only. + */ + public void requestInterrupts() { + if(m_interrupt != null){ + throw new AllocationException("The interrupt has already been allocated"); + } + + allocateInterrupts(true); + + assert (m_interrupt != null); + + ByteBuffer status = ByteBuffer.allocateDirect(4); + // set the byte order + status.order(ByteOrder.LITTLE_ENDIAN); + InterruptJNI.requestInterrupts(m_interrupt, getModuleForRouting(), + getChannelForRouting(), + (byte) (getAnalogTriggerForRouting() ? 1 : 0), status.asIntBuffer()); + HALUtil.checkStatus(status.asIntBuffer()); + setUpSourceEdge(true, false); + + } /** * Allocate the interrupt * - * @param watcher + * @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. */ - public void allocateInterrupts(boolean watcher) { - if (!watcher) { - throw new IllegalArgumentException( - "Interrupt callbacks not yet supported"); + protected void allocateInterrupts(boolean watcher) { + try { + m_interruptIndex = interrupts.allocate(); + } catch (CheckedAllocationException e) { + throw new AllocationException( + "No interrupts are left to be allocated"); } - // Expects the calling leaf class to allocate an interrupt index. - IntBuffer status = IntBuffer.allocate(1); + m_isSynchronousInterrupt = watcher; + + ByteBuffer status = ByteBuffer.allocateDirect(4); + status.order(ByteOrder.LITTLE_ENDIAN); m_interrupt = InterruptJNI.initializeInterrupts(m_interruptIndex, - (byte) (watcher ? 1 : 0), status); - HALUtil.checkStatus(status); + (byte) (watcher ? 1 : 0), status.asIntBuffer()); + HALUtil.checkStatus(status.asIntBuffer()); } /** @@ -66,13 +157,15 @@ public abstract class InterruptableSensorBase extends SensorBase { * structures and disables any interrupts. */ public void cancelInterrupts() { - if (m_interrupt == null || m_manager == null) { - throw new IllegalStateException(); + if (m_interrupt == null) { + throw new IllegalStateException("The interrupt is not allocated."); } - IntBuffer status = IntBuffer.allocate(1); - InterruptJNI.cleanInterrupts(m_interrupt, status); - HALUtil.checkStatus(status); - + ByteBuffer status = ByteBuffer.allocateDirect(4); + status.order(ByteOrder.LITTLE_ENDIAN); + InterruptJNI.cleanInterrupts(m_interrupt, status.asIntBuffer()); + HALUtil.checkStatus(status.asIntBuffer()); + m_interrupt = null; + interrupts.free(m_interruptIndex); } /** @@ -82,12 +175,13 @@ public abstract class InterruptableSensorBase extends SensorBase { * Timeout in seconds */ public void waitForInterrupt(double timeout) { - if (m_interrupt == null || m_manager == null) { - throw new IllegalStateException(); + if (m_interrupt == null) { + throw new IllegalStateException("The interrupt is not allocated."); } - IntBuffer status = IntBuffer.allocate(1); - InterruptJNI.waitForInterrupt(m_interrupt, (float) timeout, status); - HALUtil.checkStatus(status); + ByteBuffer status = ByteBuffer.allocateDirect(4); + status.order(ByteOrder.LITTLE_ENDIAN); + InterruptJNI.waitForInterrupt(m_interrupt, (float) timeout, status.asIntBuffer()); + HALUtil.checkStatus(status.asIntBuffer()); } /** @@ -96,24 +190,32 @@ public abstract class InterruptableSensorBase extends SensorBase { * other options before starting to field interrupts. */ public void enableInterrupts() { - if (m_interrupt == null || m_manager == null) { - throw new IllegalStateException(); + if (m_interrupt == null) { + throw new IllegalStateException("The interrupt is not allocated."); } - IntBuffer status = IntBuffer.allocate(1); - InterruptJNI.enableInterrupts(m_interrupt, status); - HALUtil.checkStatus(status); + if(m_isSynchronousInterrupt){ + throw new IllegalStateException("You do not need to enable synchronous interrupts"); + } + ByteBuffer status = ByteBuffer.allocateDirect(4); + status.order(ByteOrder.LITTLE_ENDIAN); + InterruptJNI.enableInterrupts(m_interrupt, status.asIntBuffer()); + HALUtil.checkStatus(status.asIntBuffer()); } /** * Disable Interrupts without without deallocating structures. */ public void disableInterrupts() { - if (m_interrupt == null || m_manager == null) { - throw new IllegalStateException(); + if (m_interrupt == null) { + throw new IllegalStateException("The interrupt is not allocated."); } - IntBuffer status = IntBuffer.allocate(1); - InterruptJNI.disableInterrupts(m_interrupt, status); - HALUtil.checkStatus(status); + if(m_isSynchronousInterrupt){ + throw new IllegalStateException("You can not disable synchronous interrupts"); + } + ByteBuffer status = ByteBuffer.allocateDirect(4); + status.order(ByteOrder.LITTLE_ENDIAN); + InterruptJNI.disableInterrupts(m_interrupt, status.asIntBuffer()); + HALUtil.checkStatus(status.asIntBuffer()); } /** @@ -123,12 +225,36 @@ public abstract class InterruptableSensorBase extends SensorBase { * @return Timestamp in seconds since boot. */ public double readInterruptTimestamp() { - if (m_interrupt == null || m_manager == null) { - throw new IllegalStateException(); + if (m_interrupt == null) { + throw new IllegalStateException("The interrupt is not allocated."); } - IntBuffer status = IntBuffer.allocate(1); - double timestamp = InterruptJNI.readInterruptTimestamp(m_interrupt, status); - HALUtil.checkStatus(status); + ByteBuffer status = ByteBuffer.allocateDirect(4); + status.order(ByteOrder.LITTLE_ENDIAN); + double timestamp = InterruptJNI.readInterruptTimestamp(m_interrupt, status.asIntBuffer()); + HALUtil.checkStatus(status.asIntBuffer()); return timestamp; } + + /** + * 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 != null) { + ByteBuffer status = ByteBuffer.allocateDirect(4); + // set the byte order + status.order(ByteOrder.LITTLE_ENDIAN); + InterruptJNI.setInterruptUpSourceEdge(m_interrupt, + (byte) (risingEdge ? 1 : 0), (byte) (fallingEdge ? 1 : 0), + status.asIntBuffer()); + HALUtil.checkStatus(status.asIntBuffer()); + } else { + throw new IllegalArgumentException( + "You must call RequestInterrupts before setUpSourceEdge"); + } + } } diff --git a/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/hal/HALUtil.java b/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/hal/HALUtil.java index 7cb5bcd464..86d5b08f5e 100644 --- a/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/hal/HALUtil.java +++ b/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/hal/HALUtil.java @@ -1,7 +1,7 @@ package edu.wpi.first.wpilibj.hal; -import java.nio.IntBuffer; import java.nio.ByteBuffer; +import java.nio.IntBuffer; public class HALUtil extends JNIWrapper { public static final int NULL_PARAMETER = -5; @@ -28,6 +28,12 @@ public class HALUtil extends JNIWrapper { public static native boolean getFPGAButton(IntBuffer status); public static native String getHALErrorMessage(int code); + + public static native int getHALErrno(); + public static native String getHALstrerror(int errno); + public static String getHALstrerror(){ + return getHALstrerror(getHALErrno()); + } public static void checkStatus(IntBuffer status) { diff --git a/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/hal/InterruptJNI.java b/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/hal/InterruptJNI.java index 23b722c66a..9e18d7a18d 100644 --- a/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/hal/InterruptJNI.java +++ b/wpilibj/wpilibJavaDevices/src/main/java/edu/wpi/first/wpilibj/hal/InterruptJNI.java @@ -1,12 +1,13 @@ package edu.wpi.first.wpilibj.hal; -import java.nio.IntBuffer; import java.nio.ByteBuffer; +import java.nio.IntBuffer; public class InterruptJNI extends JNIWrapper { - public interface InterruptHandlerFunction { - void apply(int interruptAssertedMask, ByteBuffer param); + public interface InterruptJNIHandlerFunction { + void apply(int interruptAssertedMask, Object param); }; + public static native void initializeInterruptJVM(IntBuffer status); public static native ByteBuffer initializeInterrupts(int interruptIndex, byte watcher, IntBuffer status); public static native void cleanInterrupts(ByteBuffer interrupt_pointer, IntBuffer status); public static native void waitForInterrupt(ByteBuffer interrupt_pointer, double timeout, IntBuffer status); @@ -14,6 +15,6 @@ public class InterruptJNI extends JNIWrapper { public static native void disableInterrupts(ByteBuffer interrupt_pointer, IntBuffer status); public static native double readInterruptTimestamp(ByteBuffer interrupt_pointer, IntBuffer status); public static native void requestInterrupts(ByteBuffer interrupt_pointer, byte routing_module, int routing_pin, byte routing_analog_trigger, IntBuffer status); - public static native void attachInterruptHandler(ByteBuffer interrupt_pointer, InterruptHandlerFunction handler, ByteBuffer param, IntBuffer status); + public static native void attachInterruptHandler(ByteBuffer interrupt_pointer, InterruptJNIHandlerFunction handler, Object param, IntBuffer status); public static native void setInterruptUpSourceEdge(ByteBuffer interrupt_pointer, byte risingEdge, byte fallingEdge, IntBuffer status); } diff --git a/wpilibj/wpilibJavaIntegrationTests/src/main/java/edu/wpi/first/wpilibj/AbstractInterruptTest.java b/wpilibj/wpilibJavaIntegrationTests/src/main/java/edu/wpi/first/wpilibj/AbstractInterruptTest.java new file mode 100644 index 0000000000..967bfefddb --- /dev/null +++ b/wpilibj/wpilibJavaIntegrationTests/src/main/java/edu/wpi/first/wpilibj/AbstractInterruptTest.java @@ -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{ + 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()); + } + +} diff --git a/wpilibj/wpilibJavaIntegrationTests/src/main/java/edu/wpi/first/wpilibj/AnalogCrossConnectTest.java b/wpilibj/wpilibJavaIntegrationTests/src/main/java/edu/wpi/first/wpilibj/AnalogCrossConnectTest.java index 05f77d99be..dc99d03024 100644 --- a/wpilibj/wpilibJavaIntegrationTests/src/main/java/edu/wpi/first/wpilibj/AnalogCrossConnectTest.java +++ b/wpilibj/wpilibJavaIntegrationTests/src/main/java/edu/wpi/first/wpilibj/AnalogCrossConnectTest.java @@ -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); + } } diff --git a/wpilibj/wpilibJavaIntegrationTests/src/main/java/edu/wpi/first/wpilibj/AnalogPotentiometerTest.java b/wpilibj/wpilibJavaIntegrationTests/src/main/java/edu/wpi/first/wpilibj/AnalogPotentiometerTest.java index ab8ae65b3d..0c6de20a3e 100644 --- a/wpilibj/wpilibJavaIntegrationTests/src/main/java/edu/wpi/first/wpilibj/AnalogPotentiometerTest.java +++ b/wpilibj/wpilibJavaIntegrationTests/src/main/java/edu/wpi/first/wpilibj/AnalogPotentiometerTest.java @@ -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); } } diff --git a/wpilibj/wpilibJavaIntegrationTests/src/main/java/edu/wpi/first/wpilibj/DIOCrossConnectTest.java b/wpilibj/wpilibJavaIntegrationTests/src/main/java/edu/wpi/first/wpilibj/DIOCrossConnectTest.java index ac15b97786..3ee5dd475e 100644 --- a/wpilibj/wpilibJavaIntegrationTests/src/main/java/edu/wpi/first/wpilibj/DIOCrossConnectTest.java +++ b/wpilibj/wpilibJavaIntegrationTests/src/main/java/edu/wpi/first/wpilibj/DIOCrossConnectTest.java @@ -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 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); + + } + } diff --git a/wpilibj/wpilibJavaIntegrationTests/src/main/java/edu/wpi/first/wpilibj/fixtures/DIOCrossConnectFixture.java b/wpilibj/wpilibJavaIntegrationTests/src/main/java/edu/wpi/first/wpilibj/fixtures/DIOCrossConnectFixture.java index 5088d2349c..9abd6733c0 100644 --- a/wpilibj/wpilibJavaIntegrationTests/src/main/java/edu/wpi/first/wpilibj/fixtures/DIOCrossConnectFixture.java +++ b/wpilibj/wpilibJavaIntegrationTests/src/main/java/edu/wpi/first/wpilibj/fixtures/DIOCrossConnectFixture.java @@ -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; } diff --git a/wpilibj/wpilibJavaJNI/lib/HALUtil.cpp b/wpilibj/wpilibJavaJNI/lib/HALUtil.cpp index e1491a3709..1d0bb39a4b 100644 --- a/wpilibj/wpilibJavaJNI/lib/HALUtil.cpp +++ b/wpilibj/wpilibJavaJNI/lib/HALUtil.cpp @@ -3,6 +3,8 @@ #include "Log.hpp" #include "edu_wpi_first_wpilibj_hal_HALUtil.h" #include "HAL/HAL.hpp" +#include "errno.h" +#include // set the logging level TLogLevel halUtilLogLevel = logWARNING; @@ -145,3 +147,27 @@ JNIEXPORT jstring JNICALL Java_edu_wpi_first_wpilibj_hal_HALUtil_getHALErrorMess HALUTIL_LOG(logDEBUG) << "Calling HALUtil getHALErrorMessage id=" << paramId << " msg=" << msg; return paramEnv->NewStringUTF(msg); } + +/* + * Class: edu_wpi_first_wpilibj_hal_HALUtil + * Method: getHALErrno + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_edu_wpi_first_wpilibj_hal_HALUtil_getHALErrno + (JNIEnv *, jclass) +{ + return errno; +} + +/* + * Class: edu_wpi_first_wpilibj_hal_HALUtil + * Method: getHALstrerror + * Signature: (I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_edu_wpi_first_wpilibj_hal_HALUtil_getHALstrerror + (JNIEnv * env, jclass, jint errorCode) +{ + const char * msg = strerror(errno); + HALUTIL_LOG(logDEBUG) << "Calling HALUtil getHALstrerror errorCode=" << errorCode << " msg=" << msg; + return env->NewStringUTF(msg); +} diff --git a/wpilibj/wpilibJavaJNI/lib/InterruptJNI.cpp b/wpilibj/wpilibJavaJNI/lib/InterruptJNI.cpp index 9aeea3d243..6173c8f47c 100644 --- a/wpilibj/wpilibJavaJNI/lib/InterruptJNI.cpp +++ b/wpilibj/wpilibJavaJNI/lib/InterruptJNI.cpp @@ -3,6 +3,33 @@ #include "Log.hpp" #include "edu_wpi_first_wpilibj_hal_InterruptJNI.h" +#include "HAL/Interrupts.hpp" + +TLogLevel interruptJNILogLevel = logERROR; + +#define INTERRUPTJNI_LOG(level) \ + if (level > interruptJNILogLevel) ; \ + else Log().Get(level) + +//Used for callback when an interrupt is fired. +static JavaVM *jvm; + +/* + * Class: edu_wpi_first_wpilibj_hal_InterruptJNI + * Method: initializeInterruptJVM + * Signature: (Ljava/nio/IntBuffer;)V + */ +JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_hal_InterruptJNI_initializeInterruptJVM + (JNIEnv * env, jclass, jobject status) +{ + //This method should be called once to setup the JVM + INTERRUPTJNI_LOG(logDEBUG) << "Calling INTERRUPTJNI initializeInterruptJVM"; + jint * statusPtr = (jint*)env->GetDirectBufferAddress(status); + INTERRUPTJNI_LOG(logDEBUG) << "Status Ptr = " << statusPtr; + jint rs = env->GetJavaVM(&jvm); + assert (rs == JNI_OK); +} + /* * Class: edu_wpi_first_wpilibj_hal_InterruptJNI @@ -10,22 +37,41 @@ * Signature: (IBLjava/nio/IntBuffer;)Ljava/nio/ByteBuffer; */ JNIEXPORT jobject JNICALL Java_edu_wpi_first_wpilibj_hal_InterruptJNI_initializeInterrupts - (JNIEnv *, jclass, jint, jbyte, jobject) + (JNIEnv * env, jclass, jint interruptIndex, jbyte watcher, jobject status) { - assert(false); + INTERRUPTJNI_LOG(logDEBUG) << "Calling INTERRUPTJNI initializeInterrupts"; + INTERRUPTJNI_LOG(logDEBUG) << "interruptIndex = " << interruptIndex; + INTERRUPTJNI_LOG(logDEBUG) << "watcher = " << (bool) watcher; + jint * statusPtr = (jint*)env->GetDirectBufferAddress(status); + INTERRUPTJNI_LOG(logDEBUG) << "Status Ptr = " << statusPtr; + void** interruptPtr = (void**)new unsigned char[4]; + *interruptPtr = (void**) initializeInterrupts(interruptIndex, watcher, statusPtr); + + INTERRUPTJNI_LOG(logDEBUG) << "Interrupt Ptr = " << *interruptPtr; + INTERRUPTJNI_LOG(logDEBUG) << "Status = " << *statusPtr; + + return env->NewDirectByteBuffer(interruptPtr, 4); } + /* * Class: edu_wpi_first_wpilibj_hal_InterruptJNI * Method: cleanInterrupts * Signature: (Ljava/nio/ByteBuffer;Ljava/nio/IntBuffer;)V */ JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_hal_InterruptJNI_cleanInterrupts - (JNIEnv *, jclass, jobject, jobject) + (JNIEnv * env, jclass, jobject interrupt_pointer, jobject status) { - assert(false); + INTERRUPTJNI_LOG(logDEBUG) << "Calling INTERRUPTJNI cleanInterrupts"; + void ** javaId = (void**)env->GetDirectBufferAddress(interrupt_pointer); + INTERRUPTJNI_LOG(logDEBUG) << "Interrupt Ptr = " << *javaId; + jint * statusPtr = (jint*)env->GetDirectBufferAddress(status); + INTERRUPTJNI_LOG(logDEBUG) << "Status Ptr = " << statusPtr; + cleanInterrupts(*javaId, statusPtr); + + INTERRUPTJNI_LOG(logDEBUG) << "Status = " << *statusPtr; } /* @@ -34,10 +80,17 @@ JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_hal_InterruptJNI_cleanInterrup * Signature: (Ljava/nio/ByteBuffer;DLjava/nio/IntBuffer;)V */ JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_hal_InterruptJNI_waitForInterrupt - (JNIEnv *, jclass, jobject, jdouble, jobject) + (JNIEnv * env, jclass, jobject interrupt_pointer, jdouble timeout, jobject status) { - assert(false); + INTERRUPTJNI_LOG(logDEBUG) << "Calling INTERRUPTJNI waitForInterrupt"; + void ** javaId = (void**)env->GetDirectBufferAddress(interrupt_pointer); + INTERRUPTJNI_LOG(logDEBUG) << "Interrupt Ptr = " << *javaId; + jint * statusPtr = (jint*)env->GetDirectBufferAddress(status); + INTERRUPTJNI_LOG(logDEBUG) << "Status Ptr = " << statusPtr; + waitForInterrupt(*javaId, timeout, statusPtr); + + INTERRUPTJNI_LOG(logDEBUG) << "Status = " << *statusPtr; } /* @@ -46,10 +99,17 @@ JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_hal_InterruptJNI_waitForInterr * Signature: (Ljava/nio/ByteBuffer;Ljava/nio/IntBuffer;)V */ JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_hal_InterruptJNI_enableInterrupts - (JNIEnv *, jclass, jobject, jobject) + (JNIEnv * env, jclass, jobject interrupt_pointer, jobject status) { - assert(false); + INTERRUPTJNI_LOG(logDEBUG) << "Calling INTERRUPTJNI enableInterrupts"; + void ** javaId = (void**)env->GetDirectBufferAddress(interrupt_pointer); + INTERRUPTJNI_LOG(logDEBUG) << "Interrupt Ptr = " << *javaId; + jint * statusPtr = (jint*)env->GetDirectBufferAddress(status); + INTERRUPTJNI_LOG(logDEBUG) << "Status Ptr = " << statusPtr; + enableInterrupts(*javaId, statusPtr); + + INTERRUPTJNI_LOG(logDEBUG) << "Status = " << *statusPtr; } /* @@ -58,10 +118,17 @@ JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_hal_InterruptJNI_enableInterru * Signature: (Ljava/nio/ByteBuffer;Ljava/nio/IntBuffer;)V */ JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_hal_InterruptJNI_disableInterrupts - (JNIEnv *, jclass, jobject, jobject) + (JNIEnv * env, jclass, jobject interrupt_pointer, jobject status) { - assert(false); + INTERRUPTJNI_LOG(logDEBUG) << "Calling INTERRUPTJNI disableInterrupts"; + void ** javaId = (void**)env->GetDirectBufferAddress(interrupt_pointer); + INTERRUPTJNI_LOG(logDEBUG) << "Interrupt Ptr = " << *javaId; + jint * statusPtr = (jint*)env->GetDirectBufferAddress(status); + INTERRUPTJNI_LOG(logDEBUG) << "Status Ptr = " << statusPtr; + disableInterrupts(*javaId, statusPtr); + + INTERRUPTJNI_LOG(logDEBUG) << "Status = " << *statusPtr; } /* @@ -70,10 +137,18 @@ JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_hal_InterruptJNI_disableInterr * Signature: (Ljava/nio/ByteBuffer;Ljava/nio/IntBuffer;)D */ JNIEXPORT jdouble JNICALL Java_edu_wpi_first_wpilibj_hal_InterruptJNI_readInterruptTimestamp - (JNIEnv *, jclass, jobject, jobject) + (JNIEnv * env, jclass, jobject interrupt_pointer, jobject status) { - assert(false); + INTERRUPTJNI_LOG(logDEBUG) << "Calling INTERRUPTJNI readInterruptTimestamp"; + void ** javaId = (void**)env->GetDirectBufferAddress(interrupt_pointer); + INTERRUPTJNI_LOG(logDEBUG) << "Interrupt Ptr = " << *javaId; + jint * statusPtr = (jint*)env->GetDirectBufferAddress(status); + INTERRUPTJNI_LOG(logDEBUG) << "Status Ptr = " << statusPtr; + jdouble timeStamp = readInterruptTimestamp(*javaId, statusPtr); + + INTERRUPTJNI_LOG(logDEBUG) << "Status = " << *statusPtr; + return timeStamp; } /* @@ -82,10 +157,73 @@ JNIEXPORT jdouble JNICALL Java_edu_wpi_first_wpilibj_hal_InterruptJNI_readInterr * Signature: (Ljava/nio/ByteBuffer;BIBLjava/nio/IntBuffer;)V */ JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_hal_InterruptJNI_requestInterrupts - (JNIEnv *, jclass, jobject, jbyte, jint, jbyte, jobject) + (JNIEnv * env, jclass, jobject interrupt_pointer, jbyte routing_module, jint routing_pin, jbyte routing_analog_trigger, jobject status) { - assert(false); + INTERRUPTJNI_LOG(logDEBUG) << "Calling INTERRUPTJNI requestInterrupts"; + void ** javaId = (void**)env->GetDirectBufferAddress(interrupt_pointer); + INTERRUPTJNI_LOG(logDEBUG) << "Interrupt Ptr = " << *javaId; + INTERRUPTJNI_LOG(logDEBUG) << "routing module = " << (jint) routing_module; + INTERRUPTJNI_LOG(logDEBUG) << "routing pin = " << routing_pin; + INTERRUPTJNI_LOG(logDEBUG) << "routing analog trigger = " << (jint) routing_analog_trigger; + jint * statusPtr = (jint*)env->GetDirectBufferAddress(status); + INTERRUPTJNI_LOG(logDEBUG) << "Status Ptr = " << statusPtr; + requestInterrupts(*javaId, (uint8_t) routing_module, (uint32_t) routing_pin, routing_analog_trigger, statusPtr); + + INTERRUPTJNI_LOG(logDEBUG) << "Status = " << *statusPtr; +} + + + +struct InterruptHandlerParam { + /* + * The object edu/wpi/first/wpilibj/hal/InterruptJNI/InterruptHandlerFunction + * that contains the callback method [code]mid[/code]. + */ + jobject handler_obj; + + //The method id for the callback method + jmethodID mid; + + //The params passed to the function + jobject param; + + ~InterruptHandlerParam(){ + INTERRUPTJNI_LOG(logDEBUG) << "Calling INTERRUPTJNI InterruptHandlerParam Destructor"; + JNIEnv *env; + jint rs = jvm->AttachCurrentThread((void**)&env, NULL); + assert (rs == JNI_OK); + + env->DeleteGlobalRef(handler_obj); + env->DeleteGlobalRef(param); + rs = jvm->DetachCurrentThread(); + assert (rs == JNI_OK); + INTERRUPTJNI_LOG(logDEBUG) << "Leaving INTERRUPTJNI InterruptHandlerParam Destructor"; + } +}; + +void interruptHandler(uint32_t mask, void *data) { + INTERRUPTJNI_LOG(logDEBUG) << "Calling INTERRUPTJNI interruptHandler"; + InterruptHandlerParam *param = static_cast(data); + + INTERRUPTJNI_LOG(logDEBUG) << "InterruptHandlerParam Ptr = " << param; + INTERRUPTJNI_LOG(logDEBUG) << "InterruptHandlerParam->obj = " << param->handler_obj; + INTERRUPTJNI_LOG(logDEBUG) << "InterruptHandlerParam->param = " << param->param; + + //Because this is a callback in a new thread we must attach it to the JVM + JNIEnv *env; + jint rs = jvm->AttachCurrentThread((void**)&env, NULL); + assert (rs == JNI_OK); + INTERRUPTJNI_LOG(logDEBUG) << "Attached to thread"; + + env->CallVoidMethod(param->handler_obj, param->mid, mask, param->param); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + } + + rs = jvm->DetachCurrentThread(); + assert (rs == JNI_OK); + INTERRUPTJNI_LOG(logDEBUG) << "Leaving INTERRUPTJNI interruptHandler"; } /* @@ -94,10 +232,47 @@ JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_hal_InterruptJNI_requestInterr * Signature: (Ljava/nio/ByteBuffer;Ledu/wpi/first/wpilibj/hal/InterruptJNI/InterruptHandlerFunction;Ljava/nio/ByteBuffer;Ljava/nio/IntBuffer;)V */ JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_hal_InterruptJNI_attachInterruptHandler - (JNIEnv *, jclass, jobject, jobject, jobject, jobject) + (JNIEnv * env, jclass, jobject interrupt_pointer, jobject handler, jobject param, jobject status) { - assert(false); + INTERRUPTJNI_LOG(logDEBUG) << "Calling INTERRUPTJNI attachInterruptHandler"; + void ** javaId = (void**)env->GetDirectBufferAddress(interrupt_pointer); + INTERRUPTJNI_LOG(logDEBUG) << "Interrupt Ptr = " << *javaId; + jint * statusPtr = (jint*)env->GetDirectBufferAddress(status); + INTERRUPTJNI_LOG(logDEBUG) << "Status Ptr = " << statusPtr; + + //Store the interrupt callback paramaters + InterruptHandlerParam *interruptHandlerParam = new InterruptHandlerParam(); + //Stores the object that contains the callback + interruptHandlerParam->handler_obj = env->NewGlobalRef(handler); + //The parameter that will be passed back to the JVM when the method is called + interruptHandlerParam->param = env->NewGlobalRef(param); + + jclass cls = env->GetObjectClass(handler); + INTERRUPTJNI_LOG(logDEBUG) << "class = " << cls; + if (cls == 0) { + INTERRUPTJNI_LOG(logERROR) << "Error getting java class"; + assert (false); + return; + } + + jmethodID mid = env->GetMethodID(cls, "apply", "(ILjava/lang/Object;)V"); + INTERRUPTJNI_LOG(logDEBUG) << "method = " << mid; + if (mid == 0) { + INTERRUPTJNI_LOG(logERROR) << "Error getting java method ID"; + assert (false); + return; + } + interruptHandlerParam->mid = mid; + + INTERRUPTJNI_LOG(logDEBUG) << "InterruptHandlerParam Ptr = " << interruptHandlerParam; + INTERRUPTJNI_LOG(logDEBUG) << "InterruptHandlerParam->obj (handler) = " << interruptHandlerParam->handler_obj; + INTERRUPTJNI_LOG(logDEBUG) << "InterruptHandlerParam->mid = " << interruptHandlerParam->mid; + INTERRUPTJNI_LOG(logDEBUG) << "InterruptHandlerParam->param = " << interruptHandlerParam->param; + + attachInterruptHandler(*javaId, interruptHandler, interruptHandlerParam, statusPtr); + + INTERRUPTJNI_LOG(logDEBUG) << "Status = " << *statusPtr; } /* @@ -106,8 +281,18 @@ JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_hal_InterruptJNI_attachInterru * Signature: (Ljava/nio/ByteBuffer;BBLjava/nio/IntBuffer;)V */ JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_hal_InterruptJNI_setInterruptUpSourceEdge - (JNIEnv *, jclass, jobject, jbyte, jbyte, jobject) + (JNIEnv * env, jclass, jobject interrupt_pointer, jbyte risingEdge, jbyte fallingEdge, jobject status) { - assert(false); + INTERRUPTJNI_LOG(logDEBUG) << "Calling INTERRUPTJNI setInterruptUpSourceEdge"; + void ** javaId = (void**)env->GetDirectBufferAddress(interrupt_pointer); + INTERRUPTJNI_LOG(logDEBUG) << "Interrupt Ptr = " << *javaId; + INTERRUPTJNI_LOG(logDEBUG) << "Rising Edge = " << (bool) risingEdge; + INTERRUPTJNI_LOG(logDEBUG) << "Falling Edge = " << (bool) fallingEdge; + jint * statusPtr = (jint*)env->GetDirectBufferAddress(status); + INTERRUPTJNI_LOG(logDEBUG) << "Status Ptr = " << statusPtr; + + setInterruptUpSourceEdge(*javaId, risingEdge, fallingEdge, statusPtr); + + INTERRUPTJNI_LOG(logDEBUG) << "Status = " << *statusPtr; }