From 5c817082a0dbc032a3dab4a8b6b6fbfb8b451f75 Mon Sep 17 00:00:00 2001 From: Thad House Date: Sat, 5 Jun 2021 11:25:21 -0700 Subject: [PATCH] [wpilib] Remove InterruptableSensorBase and replace with interrupt classes (#2410) --- .../java/edu/wpi/first/hal/InterruptJNI.java | 13 +- hal/src/main/native/athena/Interrupts.cpp | 127 +----- hal/src/main/native/cpp/jni/InterruptJNI.cpp | 183 +-------- hal/src/main/native/include/hal/Interrupts.h | 56 +-- hal/src/main/native/sim/Interrupts.cpp | 247 +----------- .../first/wpilibj/command/CommandGroup.java | 2 +- .../wpi/first/wpilibj/command/Scheduler.java | 2 +- wpilibc/src/main/native/cpp/AnalogTrigger.cpp | 1 + .../main/native/cpp/AnalogTriggerOutput.cpp | 2 + .../main/native/cpp/AsynchronousInterrupt.cpp | 67 ++++ .../native/cpp/InterruptableSensorBase.cpp | 156 -------- .../main/native/cpp/SynchronousInterrupt.cpp | 105 +++++ .../include/frc/AsynchronousInterrupt.h | 150 +++++++ .../main/native/include/frc/DigitalSource.h | 8 +- .../include/frc/InterruptableSensorBase.h | 148 ------- .../native/include/frc/SynchronousInterrupt.h | 98 +++++ .../src/main/native/cpp/AnalogLoopTest.cpp | 16 +- .../src/main/native/cpp/DIOLoopTest.cpp | 61 ++- .../first/wpilibj/AsynchronousInterrupt.java | 136 +++++++ .../edu/wpi/first/wpilibj/DigitalInput.java | 3 - .../edu/wpi/first/wpilibj/DigitalSource.java | 19 +- .../wpilibj/InterruptHandlerFunction.java | 50 --- .../wpilibj/InterruptableSensorBase.java | 270 ------------- .../first/wpilibj/SynchronousInterrupt.java | 159 ++++++++ .../first/wpilibj/AbstractInterruptTest.java | 369 ++++++++---------- .../first/wpilibj/AnalogCrossConnectTest.java | 9 +- .../first/wpilibj/DIOCrossConnectTest.java | 17 +- .../fixtures/DIOCrossConnectFixture.java | 5 - 28 files changed, 982 insertions(+), 1497 deletions(-) create mode 100644 wpilibc/src/main/native/cpp/AsynchronousInterrupt.cpp delete mode 100644 wpilibc/src/main/native/cpp/InterruptableSensorBase.cpp create mode 100644 wpilibc/src/main/native/cpp/SynchronousInterrupt.cpp create mode 100644 wpilibc/src/main/native/include/frc/AsynchronousInterrupt.h delete mode 100644 wpilibc/src/main/native/include/frc/InterruptableSensorBase.h create mode 100644 wpilibc/src/main/native/include/frc/SynchronousInterrupt.h create mode 100644 wpilibj/src/main/java/edu/wpi/first/wpilibj/AsynchronousInterrupt.java delete mode 100644 wpilibj/src/main/java/edu/wpi/first/wpilibj/InterruptHandlerFunction.java delete mode 100644 wpilibj/src/main/java/edu/wpi/first/wpilibj/InterruptableSensorBase.java create mode 100644 wpilibj/src/main/java/edu/wpi/first/wpilibj/SynchronousInterrupt.java diff --git a/hal/src/main/java/edu/wpi/first/hal/InterruptJNI.java b/hal/src/main/java/edu/wpi/first/hal/InterruptJNI.java index 05bc2c524b..a47a364c88 100644 --- a/hal/src/main/java/edu/wpi/first/hal/InterruptJNI.java +++ b/hal/src/main/java/edu/wpi/first/hal/InterruptJNI.java @@ -7,21 +7,13 @@ package edu.wpi.first.hal; public class InterruptJNI extends JNIWrapper { public static final int HalInvalidHandle = 0; - public interface InterruptJNIHandlerFunction { - void apply(int interruptAssertedMask, Object param); - } - - public static native int initializeInterrupts(boolean watcher); + public static native int initializeInterrupts(); public static native void cleanInterrupts(int interruptHandle); public static native int waitForInterrupt( int interruptHandle, double timeout, boolean ignorePrevious); - public static native void enableInterrupts(int interruptHandle); - - public static native void disableInterrupts(int interruptHandle); - public static native long readInterruptRisingTimestamp(int interruptHandle); public static native long readInterruptFallingTimestamp(int interruptHandle); @@ -29,9 +21,6 @@ public class InterruptJNI extends JNIWrapper { public static native void requestInterrupts( int interruptHandle, int digitalSourceHandle, int analogTriggerType); - public static native void attachInterruptHandler( - int interruptHandle, InterruptJNIHandlerFunction handler, Object param); - public static native void setInterruptUpSourceEdge( int interruptHandle, boolean risingEdge, boolean fallingEdge); diff --git a/hal/src/main/native/athena/Interrupts.cpp b/hal/src/main/native/athena/Interrupts.cpp index 4ec34cd191..943a1aa060 100644 --- a/hal/src/main/native/athena/Interrupts.cpp +++ b/hal/src/main/native/athena/Interrupts.cpp @@ -21,65 +21,14 @@ using namespace hal; namespace { -// Safe thread to allow callbacks to run on their own thread -class InterruptThread : public wpi::SafeThread { - public: - void Main() override { - std::unique_lock lock(m_mutex); - while (m_active) { - m_cond.wait(lock, [&] { return !m_active || m_notify; }); - if (!m_active) { - break; - } - m_notify = false; - HAL_InterruptHandlerFunction handler = m_handler; - uint32_t mask = m_mask; - void* param = m_param; - lock.unlock(); // don't hold mutex during callback execution - handler(mask, param); - lock.lock(); - } - } - - bool m_notify = false; - HAL_InterruptHandlerFunction m_handler; - void* m_param; - uint32_t m_mask; -}; - -class InterruptThreadOwner : public wpi::SafeThreadOwner { - public: - void SetFunc(HAL_InterruptHandlerFunction handler, void* param) { - auto thr = GetThread(); - if (!thr) - return; - thr->m_handler = handler; - thr->m_param = param; - } - - void Notify(uint32_t mask) { - auto thr = GetThread(); - if (!thr) - return; - thr->m_mask = mask; - thr->m_notify = true; - thr->m_cond.notify_one(); - } -}; struct Interrupt { std::unique_ptr anInterrupt; std::unique_ptr manager; - std::unique_ptr threadOwner = nullptr; - void* param = nullptr; }; } // namespace -static void threadedInterruptHandler(uint32_t mask, void* param) { - static_cast(param)->Notify(mask); -} - static LimitedHandleResource* interruptHandles; @@ -94,8 +43,7 @@ void InitializeInterrupts() { extern "C" { -HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher, - int32_t* status) { +HAL_InterruptHandle HAL_InitializeInterrupts(int32_t* status) { hal::init::CheckInit(); HAL_InterruptHandle handle = interruptHandles->Allocate(); if (handle == HAL_kInvalidHandle) { @@ -108,24 +56,16 @@ HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher, anInterrupt->anInterrupt.reset(tInterrupt::create(interruptIndex, status)); anInterrupt->anInterrupt->writeConfig_WaitForAck(false, status); anInterrupt->manager = std::make_unique( - (1u << interruptIndex) | (1u << (interruptIndex + 8u)), watcher, status); + (1u << interruptIndex) | (1u << (interruptIndex + 8u)), true, status); return handle; } -void* HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle, - int32_t* status) { +void HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle) { auto anInterrupt = interruptHandles->Get(interruptHandle); interruptHandles->Free(interruptHandle); if (anInterrupt == nullptr) { - return nullptr; + return; } - - if (anInterrupt->manager->isEnabled(status)) { - anInterrupt->manager->disable(status); - } - - void* param = anInterrupt->param; - return param; } int64_t HAL_WaitForInterrupt(HAL_InterruptHandle interruptHandle, @@ -150,31 +90,6 @@ int64_t HAL_WaitForInterrupt(HAL_InterruptHandle interruptHandle, return result; } -void HAL_EnableInterrupts(HAL_InterruptHandle interruptHandle, - int32_t* status) { - auto anInterrupt = interruptHandles->Get(interruptHandle); - if (anInterrupt == nullptr) { - *status = HAL_HANDLE_ERROR; - return; - } - - if (!anInterrupt->manager->isEnabled(status)) { - anInterrupt->manager->enable(status); - } -} - -void HAL_DisableInterrupts(HAL_InterruptHandle interruptHandle, - int32_t* status) { - auto anInterrupt = interruptHandles->Get(interruptHandle); - if (anInterrupt == nullptr) { - *status = HAL_HANDLE_ERROR; - return; - } - if (anInterrupt->manager->isEnabled(status)) { - anInterrupt->manager->disable(status); - } -} - int64_t HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle, int32_t* status) { auto anInterrupt = interruptHandles->Get(interruptHandle); @@ -223,40 +138,6 @@ void HAL_RequestInterrupts(HAL_InterruptHandle interruptHandle, anInterrupt->anInterrupt->writeConfig_Source_Module(routingModule, status); } -void HAL_AttachInterruptHandler(HAL_InterruptHandle interruptHandle, - HAL_InterruptHandlerFunction handler, - void* param, int32_t* status) { - auto anInterrupt = interruptHandles->Get(interruptHandle); - if (anInterrupt == nullptr) { - *status = HAL_HANDLE_ERROR; - return; - } - anInterrupt->manager->registerHandler(handler, param, status); - anInterrupt->param = param; -} - -void HAL_AttachInterruptHandlerThreaded(HAL_InterruptHandle interrupt_handle, - HAL_InterruptHandlerFunction handler, - void* param, int32_t* status) { - auto anInterrupt = interruptHandles->Get(interrupt_handle); - if (anInterrupt == nullptr) { - *status = HAL_HANDLE_ERROR; - return; - } - - anInterrupt->threadOwner = std::make_unique(); - anInterrupt->threadOwner->Start(); - anInterrupt->threadOwner->SetFunc(handler, param); - - HAL_AttachInterruptHandler(interrupt_handle, threadedInterruptHandler, - anInterrupt->threadOwner.get(), status); - - if (*status != 0) { - anInterrupt->threadOwner = nullptr; - } - anInterrupt->param = param; -} - void HAL_SetInterruptUpSourceEdge(HAL_InterruptHandle interruptHandle, HAL_Bool risingEdge, HAL_Bool fallingEdge, int32_t* status) { diff --git a/hal/src/main/native/cpp/jni/InterruptJNI.cpp b/hal/src/main/native/cpp/jni/InterruptJNI.cpp index 47d37b434e..ed56ce5410 100644 --- a/hal/src/main/native/cpp/jni/InterruptJNI.cpp +++ b/hal/src/main/native/cpp/jni/InterruptJNI.cpp @@ -17,126 +17,19 @@ using namespace hal; -// Thread where callbacks are actually performed. -// -// JNI's AttachCurrentThread() creates a Java Thread object on every -// invocation, which is both time inefficient and causes issues with Eclipse -// (which tries to keep a thread list up-to-date and thus gets swamped). -// -// Instead, this class attaches just once. When a hardware notification -// occurs, a condition variable wakes up this thread and this thread actually -// makes the call into Java. -// -// We don't want to use a FIFO here. If the user code takes too long to -// process, we will just ignore the redundant wakeup. -class InterruptThreadJNI : public wpi::SafeThread { - public: - void Main() override; - - bool m_notify = false; - uint32_t m_mask = 0; - jobject m_func = nullptr; - jmethodID m_mid; - jobject m_param = nullptr; -}; - -class InterruptJNI : public wpi::SafeThreadOwner { - public: - void SetFunc(JNIEnv* env, jobject func, jmethodID mid, jobject param); - - void Notify(uint32_t mask) { - auto thr = GetThread(); - if (!thr) { - return; - } - thr->m_notify = true; - thr->m_mask = mask; - thr->m_cond.notify_one(); - } -}; - -void InterruptJNI::SetFunc(JNIEnv* env, jobject func, jmethodID mid, - jobject param) { - auto thr = GetThread(); - if (!thr) { - return; - } - // free global references - if (thr->m_func) { - env->DeleteGlobalRef(thr->m_func); - } - if (thr->m_param) { - env->DeleteGlobalRef(thr->m_param); - } - // create global references - thr->m_func = env->NewGlobalRef(func); - thr->m_param = param ? env->NewGlobalRef(param) : nullptr; - thr->m_mid = mid; -} - -void InterruptThreadJNI::Main() { - JNIEnv* env; - JavaVMAttachArgs args; - args.version = JNI_VERSION_1_2; - args.name = const_cast("Interrupt"); - args.group = nullptr; - jint rs = GetJVM()->AttachCurrentThreadAsDaemon( - reinterpret_cast(&env), &args); - if (rs != JNI_OK) { - return; - } - - std::unique_lock lock(m_mutex); - while (m_active) { - m_cond.wait(lock, [&] { return !m_active || m_notify; }); - if (!m_active) { - break; - } - m_notify = false; - if (!m_func) { - continue; - } - jobject func = m_func; - jmethodID mid = m_mid; - uint32_t mask = m_mask; - jobject param = m_param; - lock.unlock(); // don't hold mutex during callback execution - env->CallVoidMethod(func, mid, static_cast(mask), param); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); - } - lock.lock(); - } - - // free global references - if (m_func) { - env->DeleteGlobalRef(m_func); - } - if (m_param) { - env->DeleteGlobalRef(m_param); - } - - GetJVM()->DetachCurrentThread(); -} - -void interruptHandler(uint32_t mask, void* param) { - static_cast(param)->Notify(mask); -} - extern "C" { /* * Class: edu_wpi_first_hal_InterruptJNI * Method: initializeInterrupts - * Signature: (Z)I + * Signature: ()I */ JNIEXPORT jint JNICALL Java_edu_wpi_first_hal_InterruptJNI_initializeInterrupts - (JNIEnv* env, jclass, jboolean watcher) + (JNIEnv* env, jclass) { int32_t status = 0; - HAL_InterruptHandle interrupt = HAL_InitializeInterrupts(watcher, &status); + HAL_InterruptHandle interrupt = HAL_InitializeInterrupts(&status); CheckStatusForceThrow(env, status); return (jint)interrupt; @@ -151,14 +44,7 @@ JNIEXPORT void JNICALL Java_edu_wpi_first_hal_InterruptJNI_cleanInterrupts (JNIEnv* env, jclass, jint interruptHandle) { - int32_t status = 0; - auto param = - HAL_CleanInterrupts((HAL_InterruptHandle)interruptHandle, &status); - if (param) { - delete static_cast(param); - } - - // ignore status, as an invalid handle just needs to be ignored. + HAL_CleanInterrupts((HAL_InterruptHandle)interruptHandle); } /* @@ -179,36 +65,6 @@ Java_edu_wpi_first_hal_InterruptJNI_waitForInterrupt return result; } -/* - * Class: edu_wpi_first_hal_InterruptJNI - * Method: enableInterrupts - * Signature: (I)V - */ -JNIEXPORT void JNICALL -Java_edu_wpi_first_hal_InterruptJNI_enableInterrupts - (JNIEnv* env, jclass, jint interruptHandle) -{ - int32_t status = 0; - HAL_EnableInterrupts((HAL_InterruptHandle)interruptHandle, &status); - - CheckStatus(env, status); -} - -/* - * Class: edu_wpi_first_hal_InterruptJNI - * Method: disableInterrupts - * Signature: (I)V - */ -JNIEXPORT void JNICALL -Java_edu_wpi_first_hal_InterruptJNI_disableInterrupts - (JNIEnv* env, jclass, jint interruptHandle) -{ - int32_t status = 0; - HAL_DisableInterrupts((HAL_InterruptHandle)interruptHandle, &status); - - CheckStatus(env, status); -} - /* * Class: edu_wpi_first_hal_InterruptJNI * Method: readInterruptRisingTimestamp @@ -261,37 +117,6 @@ Java_edu_wpi_first_hal_InterruptJNI_requestInterrupts CheckStatus(env, status); } -/* - * Class: edu_wpi_first_hal_InterruptJNI - * Method: attachInterruptHandler - * Signature: (ILjava/lang/Object;Ljava/lang/Object;)V - */ -JNIEXPORT void JNICALL -Java_edu_wpi_first_hal_InterruptJNI_attachInterruptHandler - (JNIEnv* env, jclass, jint interruptHandle, jobject handler, jobject param) -{ - jclass cls = env->GetObjectClass(handler); - if (cls == nullptr) { - assert(false); - return; - } - jmethodID mid = env->GetMethodID(cls, "apply", "(ILjava/lang/Object;)V"); - if (mid == nullptr) { - assert(false); - return; - } - - InterruptJNI* intr = new InterruptJNI; - intr->Start(); - intr->SetFunc(env, handler, mid, param); - - int32_t status = 0; - HAL_AttachInterruptHandler((HAL_InterruptHandle)interruptHandle, - interruptHandler, intr, &status); - - CheckStatus(env, status); -} - /* * Class: edu_wpi_first_hal_InterruptJNI * Method: setInterruptUpSourceEdge diff --git a/hal/src/main/native/include/hal/Interrupts.h b/hal/src/main/native/include/hal/Interrupts.h index 8d5168755e..948859a569 100644 --- a/hal/src/main/native/include/hal/Interrupts.h +++ b/hal/src/main/native/include/hal/Interrupts.h @@ -19,25 +19,20 @@ extern "C" { #endif -typedef void (*HAL_InterruptHandlerFunction)(uint32_t interruptAssertedMask, - void* param); - /** * Initializes an interrupt. * * @param watcher true for synchronous interrupts, false for asynchronous * @return the created interrupt handle */ -HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher, int32_t* status); +HAL_InterruptHandle HAL_InitializeInterrupts(int32_t* status); /** * Frees an interrupt. * * @param interruptHandle the interrupt handle - * @return the param passed to the interrupt, or nullptr if one - * wasn't passed. */ -void* HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle, int32_t* status); +void HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle); /** * In synchronous mode, waits for the defined interrupt to occur. @@ -52,25 +47,6 @@ int64_t HAL_WaitForInterrupt(HAL_InterruptHandle interruptHandle, double timeout, HAL_Bool ignorePrevious, int32_t* status); -/** - * Enables interrupts to occur on this input. - * - * Interrupts are disabled when the RequestInterrupt call is made. This gives - * time to do the setup of the other options before starting to field - * interrupts. - * - * @param interruptHandle the interrupt handle - */ -void HAL_EnableInterrupts(HAL_InterruptHandle interruptHandle, int32_t* status); - -/** - * Disables interrupts without without deallocating structures. - * - * @param interruptHandle the interrupt handle - */ -void HAL_DisableInterrupts(HAL_InterruptHandle interruptHandle, - int32_t* status); - /** * Returns the timestamp for the rising interrupt that occurred most recently. * @@ -110,34 +86,6 @@ void HAL_RequestInterrupts(HAL_InterruptHandle interruptHandle, HAL_AnalogTriggerType analogTriggerType, int32_t* status); -/** - * Attaches an asynchronous interrupt handler to the interrupt. - * - * This interrupt gets called directly on the FPGA interrupt thread, so will - * block other interrupts while running. - * - * @param interruptHandle the interrupt handle - * @param handler the handler function for the interrupt to call - * @param param a parameter to be passed to the handler - */ -void HAL_AttachInterruptHandler(HAL_InterruptHandle interruptHandle, - HAL_InterruptHandlerFunction handler, - void* param, int32_t* status); - -/** - * Attaches an asynchronous interrupt handler to the interrupt. - * - * This interrupt gets called on a thread specific to the interrupt, so will not - * block other interrupts. - * - * @param interruptHandle the interrupt handle - * @param handler the handler function for the interrupt to call - * @param param a parameter to be passed to the handler - */ -void HAL_AttachInterruptHandlerThreaded(HAL_InterruptHandle interruptHandle, - HAL_InterruptHandlerFunction handler, - void* param, int32_t* status); - /** * Sets the edges to trigger the interrupt on. * diff --git a/hal/src/main/native/sim/Interrupts.cpp b/hal/src/main/native/sim/Interrupts.cpp index 4bfd7819aa..3772ab039d 100644 --- a/hal/src/main/native/sim/Interrupts.cpp +++ b/hal/src/main/native/sim/Interrupts.cpp @@ -42,16 +42,12 @@ struct Interrupt { HAL_Handle portHandle; uint8_t index; HAL_AnalogTriggerType trigType; - bool watcher; int64_t risingTimestamp; int64_t fallingTimestamp; bool previousState; bool fireOnUp; bool fireOnDown; int32_t callbackId; - - void* callbackParam; - HAL_InterruptHandlerFunction callbackFunction; }; struct SynchronousWaitData { @@ -83,8 +79,7 @@ void InitializeInterrupts() { } // namespace hal::init extern "C" { -HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher, - int32_t* status) { +HAL_InterruptHandle HAL_InitializeInterrupts(int32_t* status) { hal::init::CheckInit(); HAL_InterruptHandle handle = interruptHandles->Allocate(); if (handle == HAL_kInvalidHandle) { @@ -100,19 +95,10 @@ HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher, anInterrupt->index = getHandleIndex(handle); anInterrupt->callbackId = -1; - anInterrupt->watcher = watcher; - return handle; } -void* HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle, - int32_t* status) { - HAL_DisableInterrupts(interruptHandle, status); - auto anInterrupt = interruptHandles->Get(interruptHandle); +void HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle) { interruptHandles->Free(interruptHandle); - if (anInterrupt == nullptr) { - return nullptr; - } - return anInterrupt->callbackParam; } static void ProcessInterruptDigitalSynchronous(const char* name, void* param, @@ -352,12 +338,6 @@ int64_t HAL_WaitForInterrupt(HAL_InterruptHandle interruptHandle, return WaitResult::Timeout; } - // Check to make sure we are actually an interrupt in synchronous mode - if (!interrupt->watcher) { - *status = NiFpga_Status_InvalidParameter; - return WaitResult::Timeout; - } - if (interrupt->isAnalog) { return WaitForInterruptAnalog(interruptHandle, interrupt.get(), timeout, ignorePrevious); @@ -367,196 +347,6 @@ int64_t HAL_WaitForInterrupt(HAL_InterruptHandle interruptHandle, } } -static void ProcessInterruptDigitalAsynchronous(const char* name, void* param, - const struct HAL_Value* value) { - // void* is a HAL handle - // convert to uintptr_t first, then to handle - uintptr_t handleTmp = reinterpret_cast(param); - HAL_InterruptHandle handle = static_cast(handleTmp); - auto interrupt = interruptHandles->Get(handle); - if (interrupt == nullptr) { - return; - } - // Have a valid interrupt - if (value->type != HAL_Type::HAL_BOOLEAN) { - return; - } - bool retVal = value->data.v_boolean; - // If no change in interrupt, return; - if (retVal == interrupt->previousState) { - return; - } - int32_t mask = 0; - if (interrupt->previousState) { - interrupt->previousState = retVal; - interrupt->fallingTimestamp = hal::GetFPGATime(); - mask = 1 << (8 + interrupt->index); - if (!interrupt->fireOnDown) { - return; - } - } else { - interrupt->previousState = retVal; - interrupt->risingTimestamp = hal::GetFPGATime(); - mask = 1 << (interrupt->index); - if (!interrupt->fireOnUp) { - return; - } - } - - // run callback - auto callback = interrupt->callbackFunction; - if (callback == nullptr) { - return; - } - callback(mask, interrupt->callbackParam); -} - -static void ProcessInterruptAnalogAsynchronous(const char* name, void* param, - const struct HAL_Value* value) { - // void* is a HAL handle - // convert to intptr_t first, then to handle - uintptr_t handleTmp = reinterpret_cast(param); - HAL_InterruptHandle handle = static_cast(handleTmp); - auto interrupt = interruptHandles->Get(handle); - if (interrupt == nullptr) { - return; - } - // Have a valid interrupt - if (value->type != HAL_Type::HAL_DOUBLE) { - return; - } - int32_t status = 0; - bool retVal = GetAnalogTriggerValue(interrupt->portHandle, - interrupt->trigType, &status); - if (status != 0) { - return; - } - // If no change in interrupt, return; - if (retVal == interrupt->previousState) { - return; - } - int mask = 0; - if (interrupt->previousState) { - interrupt->previousState = retVal; - interrupt->fallingTimestamp = hal::GetFPGATime(); - if (!interrupt->fireOnDown) { - return; - } - mask = 1 << (8 + interrupt->index); - } else { - interrupt->previousState = retVal; - interrupt->risingTimestamp = hal::GetFPGATime(); - if (!interrupt->fireOnUp) { - return; - } - mask = 1 << (interrupt->index); - } - - // run callback - auto callback = interrupt->callbackFunction; - if (callback == nullptr) { - return; - } - callback(mask, interrupt->callbackParam); -} - -static void EnableInterruptsDigital(HAL_InterruptHandle handle, - Interrupt* interrupt) { - int32_t status = 0; - int32_t digitalIndex = GetDigitalInputChannel(interrupt->portHandle, &status); - if (status != 0) { - return; - } - - interrupt->previousState = SimDIOData[digitalIndex].value; - - int32_t uid = SimDIOData[digitalIndex].value.RegisterCallback( - &ProcessInterruptDigitalAsynchronous, - reinterpret_cast(static_cast(handle)), false); - interrupt->callbackId = uid; -} - -static void EnableInterruptsAnalog(HAL_InterruptHandle handle, - Interrupt* interrupt) { - int32_t status = 0; - int32_t analogIndex = - GetAnalogTriggerInputIndex(interrupt->portHandle, &status); - if (status != 0) { - return; - } - - status = 0; - interrupt->previousState = GetAnalogTriggerValue( - interrupt->portHandle, interrupt->trigType, &status); - if (status != 0) { - return; - } - - int32_t uid = SimAnalogInData[analogIndex].voltage.RegisterCallback( - &ProcessInterruptAnalogAsynchronous, - reinterpret_cast(static_cast(handle)), false); - interrupt->callbackId = uid; -} - -void HAL_EnableInterrupts(HAL_InterruptHandle interruptHandle, - int32_t* status) { - auto interrupt = interruptHandles->Get(interruptHandle); - if (interrupt == nullptr) { - *status = HAL_HANDLE_ERROR; - return; - } - - // If we have not had a callback set, error out - if (interrupt->callbackFunction == nullptr) { - *status = INCOMPATIBLE_STATE; - return; - } - - // EnableInterrupts has already been called - if (interrupt->callbackId >= 0) { - // We can double enable safely. - return; - } - - if (interrupt->isAnalog) { - EnableInterruptsAnalog(interruptHandle, interrupt.get()); - } else { - EnableInterruptsDigital(interruptHandle, interrupt.get()); - } -} -void HAL_DisableInterrupts(HAL_InterruptHandle interruptHandle, - int32_t* status) { - auto interrupt = interruptHandles->Get(interruptHandle); - if (interrupt == nullptr) { - *status = HAL_HANDLE_ERROR; - return; - } - - // No need to disable if we are already disabled - if (interrupt->callbackId < 0) { - return; - } - - if (interrupt->isAnalog) { - // Do analog - int32_t status = 0; - int32_t analogIndex = - GetAnalogTriggerInputIndex(interrupt->portHandle, &status); - if (status != 0) { - return; - } - SimAnalogInData[analogIndex].voltage.CancelCallback(interrupt->callbackId); - } else { - int32_t status = 0; - int32_t digitalIndex = - GetDigitalInputChannel(interrupt->portHandle, &status); - if (status != 0) { - return; - } - SimDIOData[digitalIndex].value.CancelCallback(interrupt->callbackId); - } - interrupt->callbackId = -1; -} int64_t HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle, int32_t* status) { auto interrupt = interruptHandles->Get(interruptHandle); @@ -602,24 +392,6 @@ void HAL_RequestInterrupts(HAL_InterruptHandle interruptHandle, interrupt->trigType = analogTriggerType; interrupt->portHandle = digitalSourceHandle; } -void HAL_AttachInterruptHandler(HAL_InterruptHandle interruptHandle, - HAL_InterruptHandlerFunction handler, - void* param, int32_t* status) { - auto interrupt = interruptHandles->Get(interruptHandle); - if (interrupt == nullptr) { - *status = HAL_HANDLE_ERROR; - return; - } - - interrupt->callbackFunction = handler; - interrupt->callbackParam = param; -} - -void HAL_AttachInterruptHandlerThreaded(HAL_InterruptHandle interruptHandle, - HAL_InterruptHandlerFunction handler, - void* param, int32_t* status) { - HAL_AttachInterruptHandler(interruptHandle, handler, param, status); -} void HAL_SetInterruptUpSourceEdge(HAL_InterruptHandle interruptHandle, HAL_Bool risingEdge, HAL_Bool fallingEdge, @@ -636,6 +408,19 @@ void HAL_SetInterruptUpSourceEdge(HAL_InterruptHandle interruptHandle, void HAL_ReleaseWaitingInterrupt(HAL_InterruptHandle interruptHandle, int32_t* status) { - // Requires a fairly large rewrite to get working + auto interrupt = interruptHandles->Get(interruptHandle); + if (interrupt == nullptr) { + *status = HAL_HANDLE_ERROR; + return; + } + + synchronousInterruptHandles->ForEach( + [interruptHandle](SynchronousWaitDataHandle handle, + SynchronousWaitData* data) { + if (data->interruptHandle == interruptHandle) { + data->waitPredicate = true; + data->waitCond.notify_all(); + } + }); } } // extern "C" diff --git a/wpilibOldCommands/src/main/java/edu/wpi/first/wpilibj/command/CommandGroup.java b/wpilibOldCommands/src/main/java/edu/wpi/first/wpilibj/command/CommandGroup.java index 5a4294a81a..d9e963dba8 100644 --- a/wpilibOldCommands/src/main/java/edu/wpi/first/wpilibj/command/CommandGroup.java +++ b/wpilibOldCommands/src/main/java/edu/wpi/first/wpilibj/command/CommandGroup.java @@ -330,7 +330,7 @@ public class CommandGroup extends Command { /** * Returns whether or not this group is interruptible. A command group will be uninterruptible if - * {@link CommandGroup#setInterruptible(boolean) setInterruptable(false)} was called or if it is + * {@link CommandGroup#setInterruptible(boolean) setInterruptible(false)} was called or if it is * currently running an uninterruptible command or child. * * @return whether or not this {@link CommandGroup} is interruptible. diff --git a/wpilibOldCommands/src/main/java/edu/wpi/first/wpilibj/command/Scheduler.java b/wpilibOldCommands/src/main/java/edu/wpi/first/wpilibj/command/Scheduler.java index 7d2c27fede..56a5106573 100644 --- a/wpilibOldCommands/src/main/java/edu/wpi/first/wpilibj/command/Scheduler.java +++ b/wpilibOldCommands/src/main/java/edu/wpi/first/wpilibj/command/Scheduler.java @@ -125,7 +125,7 @@ public final class Scheduler implements Sendable, AutoCloseable { /** * Adds a command immediately to the {@link Scheduler}. This should only be called in the {@link * Scheduler#run()} loop. Any command with conflicting requirements will be removed, unless it is - * uninterruptable. Giving null does nothing. + * uninterruptible. Giving null does nothing. * * @param command the {@link Command} to add */ diff --git a/wpilibc/src/main/native/cpp/AnalogTrigger.cpp b/wpilibc/src/main/native/cpp/AnalogTrigger.cpp index 2311eea6cc..7ff8ab2ab5 100644 --- a/wpilibc/src/main/native/cpp/AnalogTrigger.cpp +++ b/wpilibc/src/main/native/cpp/AnalogTrigger.cpp @@ -6,6 +6,7 @@ #include +#include #include #include diff --git a/wpilibc/src/main/native/cpp/AnalogTriggerOutput.cpp b/wpilibc/src/main/native/cpp/AnalogTriggerOutput.cpp index 722a5a06f0..80ad3fa8c7 100644 --- a/wpilibc/src/main/native/cpp/AnalogTriggerOutput.cpp +++ b/wpilibc/src/main/native/cpp/AnalogTriggerOutput.cpp @@ -4,9 +4,11 @@ #include "frc/AnalogTriggerOutput.h" +#include #include #include "frc/AnalogTrigger.h" +#include "frc/AnalogTriggerType.h" #include "frc/Errors.h" using namespace frc; diff --git a/wpilibc/src/main/native/cpp/AsynchronousInterrupt.cpp b/wpilibc/src/main/native/cpp/AsynchronousInterrupt.cpp new file mode 100644 index 0000000000..d45d5e4a5c --- /dev/null +++ b/wpilibc/src/main/native/cpp/AsynchronousInterrupt.cpp @@ -0,0 +1,67 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "frc/AsynchronousInterrupt.h" + +#include + +using namespace frc; + +AsynchronousInterrupt::AsynchronousInterrupt( + DigitalSource& source, std::function callback) + : m_interrupt{source}, m_callback{std::move(callback)} {} +AsynchronousInterrupt::AsynchronousInterrupt( + DigitalSource* source, std::function callback) + : m_interrupt{source}, m_callback{std::move(callback)} {} +AsynchronousInterrupt::AsynchronousInterrupt( + std::shared_ptr source, + std::function callback) + : m_interrupt{source}, m_callback{std::move(callback)} {} + +AsynchronousInterrupt::~AsynchronousInterrupt() { + Disable(); +} + +void AsynchronousInterrupt::ThreadMain() { + while (m_keepRunning) { + auto result = m_interrupt.WaitForInterrupt(10_s, false); + if (!m_keepRunning) { + break; + } + if (result == SynchronousInterrupt::WaitResult::kTimeout) { + continue; + } + m_callback((result & SynchronousInterrupt::WaitResult::kRisingEdge) != 0, + (result & SynchronousInterrupt::WaitResult::kFallingEdge) != 0); + } +} + +void AsynchronousInterrupt::Enable() { + if (m_keepRunning) { + return; + } + + m_keepRunning = true; + m_thread = std::thread([this] { this->ThreadMain(); }); +} + +void AsynchronousInterrupt::Disable() { + m_keepRunning = false; + m_interrupt.WakeupWaitingInterrupt(); + if (m_thread.joinable()) { + m_thread.join(); + } +} + +void AsynchronousInterrupt::SetInterruptEdges(bool risingEdge, + bool fallingEdge) { + m_interrupt.SetInterruptEdges(risingEdge, fallingEdge); +} + +units::second_t AsynchronousInterrupt::GetRisingTimestamp() { + return m_interrupt.GetRisingTimestamp(); +} +units::second_t AsynchronousInterrupt::GetFallingTimestamp() { + return m_interrupt.GetFallingTimestamp(); +} diff --git a/wpilibc/src/main/native/cpp/InterruptableSensorBase.cpp b/wpilibc/src/main/native/cpp/InterruptableSensorBase.cpp deleted file mode 100644 index f899430cfa..0000000000 --- a/wpilibc/src/main/native/cpp/InterruptableSensorBase.cpp +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) FIRST and other WPILib contributors. -// Open Source Software; you can modify and/or share it under the terms of -// the WPILib BSD license file in the root directory of this project. - -#include "frc/InterruptableSensorBase.h" - -#include "frc/Errors.h" - -using namespace frc; - -InterruptableSensorBase::~InterruptableSensorBase() { - if (m_interrupt == HAL_kInvalidHandle) { - return; - } - int32_t status = 0; - HAL_CleanInterrupts(m_interrupt, &status); - // Ignore status, as an invalid handle just needs to be ignored. -} - -void InterruptableSensorBase::RequestInterrupts( - HAL_InterruptHandlerFunction handler, void* param) { - FRC_Assert(m_interrupt == HAL_kInvalidHandle); - AllocateInterrupts(false); - - int32_t status = 0; - HAL_RequestInterrupts( - m_interrupt, GetPortHandleForRouting(), - static_cast(GetAnalogTriggerTypeForRouting()), - &status); - SetUpSourceEdge(true, false); - HAL_AttachInterruptHandler(m_interrupt, handler, param, &status); - FRC_CheckErrorStatus(status, "{}", "AttachInterruptHandler"); -} - -void InterruptableSensorBase::RequestInterrupts(InterruptEventHandler handler) { - FRC_Assert(m_interrupt == HAL_kInvalidHandle); - AllocateInterrupts(false); - - m_interruptHandler = - std::make_unique(std::move(handler)); - - int32_t status = 0; - HAL_RequestInterrupts( - m_interrupt, GetPortHandleForRouting(), - static_cast(GetAnalogTriggerTypeForRouting()), - &status); - SetUpSourceEdge(true, false); - HAL_AttachInterruptHandler( - m_interrupt, - [](uint32_t mask, void* param) { - auto self = reinterpret_cast(param); - // Rising edge result is the interrupt bit set in the byte 0xFF - // Falling edge result is the interrupt bit set in the byte 0xFF00 - // Set any bit set to be true for that edge, and AND the 2 results - // together to match the existing enum for all interrupts - int32_t rising = (mask & 0xFF) ? 0x1 : 0x0; - int32_t falling = ((mask & 0xFF00) ? 0x0100 : 0x0); - WaitResult res = static_cast(falling | rising); - (*self)(res); - }, - m_interruptHandler.get(), &status); - FRC_CheckErrorStatus(status, "{}", "AttachInterruptHandler"); -} - -void InterruptableSensorBase::RequestInterrupts() { - FRC_Assert(m_interrupt == HAL_kInvalidHandle); - AllocateInterrupts(true); - - int32_t status = 0; - HAL_RequestInterrupts( - m_interrupt, GetPortHandleForRouting(), - static_cast(GetAnalogTriggerTypeForRouting()), - &status); - FRC_CheckErrorStatus(status, "{}", "RequestInterrupts"); - SetUpSourceEdge(true, false); -} - -void InterruptableSensorBase::CancelInterrupts() { - FRC_Assert(m_interrupt != HAL_kInvalidHandle); - int32_t status = 0; - HAL_CleanInterrupts(m_interrupt, &status); - // Ignore status, as an invalid handle just needs to be ignored. - m_interrupt = HAL_kInvalidHandle; - m_interruptHandler = nullptr; -} - -InterruptableSensorBase::WaitResult InterruptableSensorBase::WaitForInterrupt( - units::second_t timeout, bool ignorePrevious) { - FRC_Assert(m_interrupt != HAL_kInvalidHandle); - int32_t status = 0; - int result; - - result = HAL_WaitForInterrupt(m_interrupt, timeout.to(), - ignorePrevious, &status); - FRC_CheckErrorStatus(status, "{}", "WaitForInterrupt"); - - // Rising edge result is the interrupt bit set in the byte 0xFF - // Falling edge result is the interrupt bit set in the byte 0xFF00 - // Set any bit set to be true for that edge, and AND the 2 results - // together to match the existing enum for all interrupts - int32_t rising = (result & 0xFF) ? 0x1 : 0x0; - int32_t falling = ((result & 0xFF00) ? 0x0100 : 0x0); - return static_cast(falling | rising); -} - -void InterruptableSensorBase::EnableInterrupts() { - FRC_Assert(m_interrupt != HAL_kInvalidHandle); - int32_t status = 0; - HAL_EnableInterrupts(m_interrupt, &status); - FRC_CheckErrorStatus(status, "{}", "EnableInterrupts"); -} - -void InterruptableSensorBase::DisableInterrupts() { - FRC_Assert(m_interrupt != HAL_kInvalidHandle); - int32_t status = 0; - HAL_DisableInterrupts(m_interrupt, &status); - FRC_CheckErrorStatus(status, "{}", "DisableInterrupts"); -} - -units::second_t InterruptableSensorBase::ReadRisingTimestamp() { - FRC_Assert(m_interrupt != HAL_kInvalidHandle); - int32_t status = 0; - int64_t timestamp = HAL_ReadInterruptRisingTimestamp(m_interrupt, &status); - FRC_CheckErrorStatus(status, "{}", "ReadRisingTimestamp"); - return units::microsecond_t(timestamp); -} - -units::second_t InterruptableSensorBase::ReadFallingTimestamp() { - FRC_Assert(m_interrupt != HAL_kInvalidHandle); - int32_t status = 0; - int64_t timestamp = HAL_ReadInterruptFallingTimestamp(m_interrupt, &status); - FRC_CheckErrorStatus(status, "{}", "ReadFallingTimestamp"); - return units::microsecond_t(timestamp); -} - -void InterruptableSensorBase::SetUpSourceEdge(bool risingEdge, - bool fallingEdge) { - if (m_interrupt == HAL_kInvalidHandle) { - throw FRC_MakeError( - err::NullParameter, "{}", - "You must call RequestInterrupts before SetUpSourceEdge"); - } - if (m_interrupt != HAL_kInvalidHandle) { - int32_t status = 0; - HAL_SetInterruptUpSourceEdge(m_interrupt, risingEdge, fallingEdge, &status); - FRC_CheckErrorStatus(status, "{}", "SetUpSourceEdge"); - } -} - -void InterruptableSensorBase::AllocateInterrupts(bool watcher) { - FRC_Assert(m_interrupt == HAL_kInvalidHandle); - // Expects the calling leaf class to allocate an interrupt index. - int32_t status = 0; - m_interrupt = HAL_InitializeInterrupts(watcher, &status); - FRC_CheckErrorStatus(status, "{}", "AllocateInterrupts"); -} diff --git a/wpilibc/src/main/native/cpp/SynchronousInterrupt.cpp b/wpilibc/src/main/native/cpp/SynchronousInterrupt.cpp new file mode 100644 index 0000000000..54b740a708 --- /dev/null +++ b/wpilibc/src/main/native/cpp/SynchronousInterrupt.cpp @@ -0,0 +1,105 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "frc/SynchronousInterrupt.h" + +#include + +#include +#include + +#include "frc/DigitalSource.h" +#include "frc/Errors.h" + +using namespace frc; + +SynchronousInterrupt::SynchronousInterrupt(DigitalSource& source) + : m_source{&source, wpi::NullDeleter()} { + InitSynchronousInterrupt(); +} +SynchronousInterrupt::SynchronousInterrupt(DigitalSource* source) + : m_source{source, wpi::NullDeleter()} { + if (m_source == nullptr) { + FRC_CheckErrorStatus(frc::err::NullParameter, "{}", "Source is null"); + } else { + InitSynchronousInterrupt(); + } +} +SynchronousInterrupt::SynchronousInterrupt( + std::shared_ptr source) + : m_source{std::move(source)} { + if (m_source == nullptr) { + FRC_CheckErrorStatus(frc::err::NullParameter, "{}", "Source is null"); + } else { + InitSynchronousInterrupt(); + } +} + +void SynchronousInterrupt::InitSynchronousInterrupt() { + int32_t status = 0; + m_handle = HAL_InitializeInterrupts(&status); + FRC_CheckErrorStatus(status, "{}", "Interrupt failed to initialize"); + HAL_RequestInterrupts(m_handle, m_source->GetPortHandleForRouting(), + static_cast( + m_source->GetAnalogTriggerTypeForRouting()), + &status); + FRC_CheckErrorStatus(status, "{}", "Interrupt request failed"); + HAL_SetInterruptUpSourceEdge(m_handle, true, false, &status); + FRC_CheckErrorStatus(status, "{}", "Interrupt setting up source edge failed"); +} + +SynchronousInterrupt::~SynchronousInterrupt() { + HAL_CleanInterrupts(m_handle); +} + +inline SynchronousInterrupt::WaitResult operator|( + SynchronousInterrupt::WaitResult lhs, + SynchronousInterrupt::WaitResult rhs) { + using T = std::underlying_type_t; + return static_cast(static_cast(lhs) | + static_cast(rhs)); +} + +SynchronousInterrupt::WaitResult SynchronousInterrupt::WaitForInterrupt( + units::second_t timeout, bool ignorePrevious) { + int32_t status = 0; + auto result = HAL_WaitForInterrupt(m_handle, timeout.to(), + ignorePrevious, &status); + + auto rising = + ((result & 0xFF) != 0) ? WaitResult::kRisingEdge : WaitResult::kTimeout; + auto falling = ((result & 0xFF00) != 0) ? WaitResult::kFallingEdge + : WaitResult::kTimeout; + + return rising | falling; +} + +void SynchronousInterrupt::SetInterruptEdges(bool risingEdge, + bool fallingEdge) { + int32_t status = 0; + HAL_SetInterruptUpSourceEdge(m_handle, risingEdge, fallingEdge, &status); + FRC_CheckErrorStatus(status, "{}", "Interrupt setting edges failed"); +} + +void SynchronousInterrupt::WakeupWaitingInterrupt() { + int32_t status = 0; + HAL_ReleaseWaitingInterrupt(m_handle, &status); + FRC_CheckErrorStatus(status, "{}", "Interrupt wakeup failed"); +} + +units::second_t SynchronousInterrupt::GetRisingTimestamp() { + int32_t status = 0; + auto ts = HAL_ReadInterruptRisingTimestamp(m_handle, &status); + FRC_CheckErrorStatus(status, "{}", "Interrupt rising timestamp failed"); + units::microsecond_t ms{static_cast(ts)}; + return ms; +} + +units::second_t SynchronousInterrupt::GetFallingTimestamp() { + int32_t status = 0; + auto ts = HAL_ReadInterruptFallingTimestamp(m_handle, &status); + FRC_CheckErrorStatus(status, "{}", "Interrupt falling timestamp failed"); + units::microsecond_t ms{static_cast(ts)}; + return ms; +} diff --git a/wpilibc/src/main/native/include/frc/AsynchronousInterrupt.h b/wpilibc/src/main/native/include/frc/AsynchronousInterrupt.h new file mode 100644 index 0000000000..924c741650 --- /dev/null +++ b/wpilibc/src/main/native/include/frc/AsynchronousInterrupt.h @@ -0,0 +1,150 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include + +#include +#include +#include +#include +#include + +namespace frc { +/** + * Class for handling asynchronous interrupts. + * + *

By default, interrupts will occur on rising edge. Callbacks are disabled + * by default, and Enable() must be called before they will occur. + * + *

Both rising and falling edge can be indiciated if both a rising and + * falling happen between callbacks. + * + *

Synchronous interrupts are handled by the SynchronousInterrupt class. + */ +class AsynchronousInterrupt { + public: + /** + * Construct an Asynchronous Interrupt from a Digital Source. + * + *

At construction, the interrupt will trigger on the rising edge. + * + *

The first bool in the callback is rising, the 2nd is falling. + */ + AsynchronousInterrupt(DigitalSource& source, + std::function callback); + + /** + * Construct an Asynchronous Interrupt from a Digital Source. + * + *

At construction, the interrupt will trigger on the rising edge. + * + *

The first bool in the callback is rising, the 2nd is falling. + */ + AsynchronousInterrupt(DigitalSource* source, + std::function callback); + + /** + * Construct an Asynchronous Interrupt from a Digital Source. + * + *

At construction, the interrupt will trigger on the rising edge. + * + *

The first bool in the callback is rising, the 2nd is falling. + */ + AsynchronousInterrupt(std::shared_ptr source, + std::function callback); + + /** + * Construct an Asynchronous Interrupt from a Digital Source. + * + *

At construction, the interrupt will trigger on the rising edge. + * + *

The first bool in the callback is rising, the 2nd is falling. + */ + template + AsynchronousInterrupt(DigitalSource& source, Callable&& f, Arg&& arg, + Args&&... args) + : AsynchronousInterrupt( + source, std::bind(std::forward(f), std::forward(arg), + std::forward(args)...)) {} + + /** + * Construct an Asynchronous Interrupt from a Digital Source. + * + *

At construction, the interrupt will trigger on the rising edge. + * + *

The first bool in the callback is rising, the 2nd is falling. + */ + template + AsynchronousInterrupt(DigitalSource* source, Callable&& f, Arg&& arg, + Args&&... args) + : AsynchronousInterrupt( + source, std::bind(std::forward(f), std::forward(arg), + std::forward(args)...)) {} + + /** + * Construct an Asynchronous Interrupt from a Digital Source. + * + *

At construction, the interrupt will trigger on the rising edge. + * + *

The first bool in the callback is rising, the 2nd is falling. + */ + template + AsynchronousInterrupt(std::shared_ptr source, Callable&& f, + Arg&& arg, Args&&... args) + : AsynchronousInterrupt( + source, std::bind(std::forward(f), std::forward(arg), + std::forward(args)...)) {} + + ~AsynchronousInterrupt(); + + /** + * Enables interrupt callbacks. Before this, callbacks will not occur. Does + * nothing if already enabled. + */ + void Enable(); + + /** + * Disables interrupt callbacks. Does nothing if already disabled. + */ + void Disable(); + + /** + * Set which edges to trigger the interrupt on. + * + * @param risingEdge Trigger on rising edge + * @param fallingEdge Trigger on falling edge + */ + void SetInterruptEdges(bool risingEdge, bool fallingEdge); + + /** + * Get the timestamp of the last rising edge. + * + *

This function does not require the interrupt to be enabled to work. + * + *

This only works if rising edge was configured using SetInterruptEdges. + * @return the timestamp in seconds relative to getFPGATime + */ + units::second_t GetRisingTimestamp(); + + /** + * Get the timestamp of the last falling edge. + * + *

This function does not require the interrupt to be enabled to work. + * + *

This only works if falling edge was configured using SetInterruptEdges. + * @return the timestamp in seconds relative to getFPGATime + */ + units::second_t GetFallingTimestamp(); + + private: + void ThreadMain(); + + std::atomic_bool m_keepRunning{false}; + std::thread m_thread; + SynchronousInterrupt m_interrupt; + std::function m_callback; +}; +} // namespace frc diff --git a/wpilibc/src/main/native/include/frc/DigitalSource.h b/wpilibc/src/main/native/include/frc/DigitalSource.h index b29c37987c..0dd35d615e 100644 --- a/wpilibc/src/main/native/include/frc/DigitalSource.h +++ b/wpilibc/src/main/native/include/frc/DigitalSource.h @@ -6,7 +6,7 @@ #include -#include "frc/InterruptableSensorBase.h" +#include "frc/AnalogTriggerType.h" namespace frc { @@ -19,14 +19,14 @@ namespace frc { * constructed and freed when finished for the source. The source can either be * a digital input or analog trigger but not both. */ -class DigitalSource : public InterruptableSensorBase { +class DigitalSource { public: DigitalSource() = default; DigitalSource(DigitalSource&&) = default; DigitalSource& operator=(DigitalSource&&) = default; - HAL_Handle GetPortHandleForRouting() const override = 0; - AnalogTriggerType GetAnalogTriggerTypeForRouting() const override = 0; + virtual HAL_Handle GetPortHandleForRouting() const = 0; + virtual AnalogTriggerType GetAnalogTriggerTypeForRouting() const = 0; virtual bool IsAnalogTrigger() const = 0; virtual int GetChannel() const = 0; }; diff --git a/wpilibc/src/main/native/include/frc/InterruptableSensorBase.h b/wpilibc/src/main/native/include/frc/InterruptableSensorBase.h deleted file mode 100644 index 1a44f400d1..0000000000 --- a/wpilibc/src/main/native/include/frc/InterruptableSensorBase.h +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright (c) FIRST and other WPILib contributors. -// Open Source Software; you can modify and/or share it under the terms of -// the WPILib BSD license file in the root directory of this project. - -#pragma once - -#include -#include - -#include -#include - -#include "frc/AnalogTriggerType.h" - -namespace frc { - -class InterruptableSensorBase { - public: - enum WaitResult { - kTimeout = 0x0, - kRisingEdge = 0x1, - kFallingEdge = 0x100, - kBoth = 0x101, - }; - - /** - * Handler for interrupts. - * - * First parameter is if rising, 2nd is if falling. - */ - using InterruptEventHandler = std::function; - - InterruptableSensorBase() = default; - - /** - * Free the resources for an interrupt event. - */ - virtual ~InterruptableSensorBase(); - - InterruptableSensorBase(InterruptableSensorBase&&) = default; - InterruptableSensorBase& operator=(InterruptableSensorBase&&) = default; - - virtual HAL_Handle GetPortHandleForRouting() const = 0; - virtual AnalogTriggerType GetAnalogTriggerTypeForRouting() const = 0; - - /** - * Request one of the 8 interrupts asynchronously on this digital input. - * - * Request interrupts in asynchronous mode where the user's interrupt handler - * will be called when the interrupt fires. Users that want control over the - * thread priority should use the synchronous method with their own spawned - * thread. The default is interrupt on rising edges only. - */ - virtual void RequestInterrupts(HAL_InterruptHandlerFunction handler, - void* param); - - /** - * Request one of the 8 interrupts asynchronously on this digital input. - * - * Request interrupts in asynchronous mode where the user's interrupt handler - * will be called when the interrupt fires. Users that want control over the - * thread priority should use the synchronous method with their own spawned - * thread. The default is interrupt on rising edges only. - */ - virtual void RequestInterrupts(InterruptEventHandler handler); - - /** - * Request one of the 8 interrupts synchronously on this digital input. - * - * Request interrupts in synchronous mode where the user program will have to - * explicitly wait for the interrupt to occur using WaitForInterrupt. - * The default is interrupt on rising edges only. - */ - virtual void RequestInterrupts(); - - /** - * Cancel interrupts on this device. - * - * This deallocates all the chipobject structures and disables any interrupts. - */ - virtual void CancelInterrupts(); - - /** - * In synchronous mode, wait for the defined interrupt to occur. - * - * You should NOT attempt to read the sensor from another thread while - * waiting for an interrupt. This is not threadsafe, and can cause memory - * corruption - * - * @param timeout Timeout - * @param ignorePrevious If true, ignore interrupts that happened before - * WaitForInterrupt was called. - * @return What interrupts fired - */ - virtual WaitResult WaitForInterrupt(units::second_t timeout, - bool ignorePrevious = true); - - /** - * Enable interrupts to occur on this input. - * - * Interrupts are disabled when the RequestInterrupt call is made. This gives - * time to do the setup of the other options before starting to field - * interrupts. - */ - virtual void EnableInterrupts(); - - /** - * Disable Interrupts without without deallocating structures. - */ - virtual void DisableInterrupts(); - - /** - * Return the timestamp for the rising interrupt that occurred most recently. - * - * This is in the same time domain as GetClock(). The rising-edge interrupt - * should be enabled with SetUpSourceEdge(). - * - * @return Timestamp in seconds since boot. - */ - virtual units::second_t ReadRisingTimestamp(); - - /** - * Return the timestamp for the falling interrupt that occurred most recently. - * - * This is in the same time domain as GetClock(). - * The falling-edge interrupt should be enabled with - * {@link #DigitalInput.SetUpSourceEdge} - * - * @return Timestamp in seconds since boot. - */ - virtual units::second_t ReadFallingTimestamp(); - - /** - * Set which edge to trigger interrupts on - * - * @param risingEdge true to interrupt on rising edge - * @param fallingEdge true to interrupt on falling edge - */ - virtual void SetUpSourceEdge(bool risingEdge, bool fallingEdge); - - protected: - hal::Handle m_interrupt; - std::unique_ptr m_interruptHandler{nullptr}; - - void AllocateInterrupts(bool watcher); -}; - -} // namespace frc diff --git a/wpilibc/src/main/native/include/frc/SynchronousInterrupt.h b/wpilibc/src/main/native/include/frc/SynchronousInterrupt.h new file mode 100644 index 0000000000..b7acd719b6 --- /dev/null +++ b/wpilibc/src/main/native/include/frc/SynchronousInterrupt.h @@ -0,0 +1,98 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include + +#include +#include + +namespace frc { +class DigitalSource; + +/** + * Class for handling ssynchronous interrupts. + * + *

By default, interrupts will occur on rising edge. + * + *

Asynchronous interrupts are handled by the AsynchronousInterrupt class. + */ +class SynchronousInterrupt { + public: + enum WaitResult { + kTimeout = 0x0, + kRisingEdge = 0x1, + kFallingEdge = 0x100, + kBoth = 0x101, + }; + + /** + * Construct a Synchronous Interrupt from a Digital Source. + */ + explicit SynchronousInterrupt(DigitalSource& source); + + /** + * Construct a Synchronous Interrupt from a Digital Source. + */ + explicit SynchronousInterrupt(DigitalSource* source); + + /** + * Construct a Synchronous Interrupt from a Digital Source. + */ + explicit SynchronousInterrupt(std::shared_ptr source); + + ~SynchronousInterrupt(); + + SynchronousInterrupt(SynchronousInterrupt&&) = default; + SynchronousInterrupt& operator=(SynchronousInterrupt&&) = default; + + /** + * Wait for an interrupt to occur. + * + *

Both rising and falling edge can be returned if both a rising and + * falling happened between calls, and ignorePrevious is false. + * + * @param timeout The timeout to wait for. 0s or less will return immediately. + * @param ignorePrevious True to ignore any previous interrupts, false to + * return interrupt value if an interrupt has occured since last call. + * @return The edge(s) that were triggered, or timeout. + */ + WaitResult WaitForInterrupt(units::second_t timeout, + bool ignorePrevious = true); + + /** + * Set which edges cause an interrupt to occur. + * + * @param risingEdge true to trigger on rising edge, false otherwise. + * @param fallingEdge true to trigger on falling edge, false otherwise + */ + void SetInterruptEdges(bool risingEdge, bool fallingEdge); + + /** + * Get the timestamp (relative to FPGA Time) of the last rising edge. + */ + units::second_t GetRisingTimestamp(); + + /** + * Get the timestamp of the last falling edge. + * + *

This function does not require the interrupt to be enabled to work. + * + *

This only works if falling edge was configured using setInterruptEdges. + * @return the timestamp in seconds relative to getFPGATime + */ + units::second_t GetFallingTimestamp(); + + /** + * Wake up an existing wait call. Can be called from any thread. + */ + void WakeupWaitingInterrupt(); + + private: + void InitSynchronousInterrupt(); + std::shared_ptr m_source; + hal::Handle m_handle; +}; +} // namespace frc diff --git a/wpilibcIntegrationTests/src/main/native/cpp/AnalogLoopTest.cpp b/wpilibcIntegrationTests/src/main/native/cpp/AnalogLoopTest.cpp index 6e1d968c6b..3540095b0c 100644 --- a/wpilibcIntegrationTests/src/main/native/cpp/AnalogLoopTest.cpp +++ b/wpilibcIntegrationTests/src/main/native/cpp/AnalogLoopTest.cpp @@ -8,6 +8,7 @@ #include "frc/AnalogInput.h" #include "frc/AnalogOutput.h" #include "frc/AnalogTrigger.h" +#include "frc/AsynchronousInterrupt.h" #include "frc/Counter.h" #include "frc/Timer.h" #include "gtest/gtest.h" @@ -91,10 +92,6 @@ TEST(AnalogLoopTest, AnalogTriggerCounterWorks) { << "Analog trigger counter did not count 50 ticks"; } -static void InterruptHandler(uint32_t interruptAssertedMask, void* param) { - *reinterpret_cast(param) = 12345; -} - TEST(AnalogLoopTest, AsynchronusInterruptWorks) { frc::AnalogInput input{TestBench::kFakeAnalogOutputChannel}; frc::AnalogOutput output{TestBench::kAnalogOutputChannel}; @@ -106,16 +103,19 @@ TEST(AnalogLoopTest, AsynchronusInterruptWorks) { // Given an interrupt handler that sets an int32_t to 12345 std::shared_ptr triggerOutput = trigger.CreateOutput(frc::AnalogTriggerType::kState); - triggerOutput->RequestInterrupts(InterruptHandler, ¶m); - triggerOutput->EnableInterrupts(); + + frc::AsynchronousInterrupt interrupt{triggerOutput, + [&](auto a, auto b) { param = 12345; }}; + + interrupt.Enable(); // If the analog output moves from below to above the window output.SetVoltage(0.0); frc::Wait(kDelayTime); output.SetVoltage(5.0); - triggerOutput->CancelInterrupts(); - // Then the int32_t should be 12345 frc::Wait(kDelayTime); + interrupt.Disable(); + EXPECT_EQ(12345, param) << "The interrupt did not run."; } diff --git a/wpilibcIntegrationTests/src/main/native/cpp/DIOLoopTest.cpp b/wpilibcIntegrationTests/src/main/native/cpp/DIOLoopTest.cpp index 215c32b9c4..d1872e9ee0 100644 --- a/wpilibcIntegrationTests/src/main/native/cpp/DIOLoopTest.cpp +++ b/wpilibcIntegrationTests/src/main/native/cpp/DIOLoopTest.cpp @@ -10,8 +10,9 @@ #include #include "TestBench.h" +#include "frc/AsynchronousInterrupt.h" #include "frc/Counter.h" -#include "frc/InterruptableSensorBase.h" +#include "frc/SynchronousInterrupt.h" #include "frc/Timer.h" #include "gtest/gtest.h" @@ -71,10 +72,10 @@ TEST_F(DIOLoopTest, DIOPWM) { m_output.EnablePWM(0.0); frc::Wait(0.5_s); m_output.UpdateDutyCycle(0.5); - m_input.RequestInterrupts(); - m_input.SetUpSourceEdge(false, true); - frc::InterruptableSensorBase::WaitResult result = - m_input.WaitForInterrupt(3_s, true); + frc::SynchronousInterrupt interrupt{m_output}; + interrupt.SetInterruptEdges(false, true); + frc::SynchronousInterrupt::WaitResult result = + interrupt.WaitForInterrupt(3_s, true); frc::Wait(0.5_s); bool firstCycle = m_input.Get(); @@ -96,7 +97,7 @@ TEST_F(DIOLoopTest, DIOPWM) { frc::Wait(0.5_s); bool secondAfterStop = m_input.Get(); - EXPECT_EQ(frc::InterruptableSensorBase::WaitResult::kFallingEdge, result) + EXPECT_EQ(frc::SynchronousInterrupt::WaitResult::kFallingEdge, result) << "WaitForInterrupt was not falling."; EXPECT_FALSE(firstCycle) << "Input not low after first delay"; @@ -132,48 +133,46 @@ TEST_F(DIOLoopTest, FakeCounter) { EXPECT_EQ(100, counter.Get()) << "Counter did not count up to 100."; } -static void InterruptHandler(uint32_t interruptAssertedMask, void* param) { - *reinterpret_cast(param) = 12345; -} - TEST_F(DIOLoopTest, AsynchronousInterruptWorks) { + Reset(); + int32_t param = 0; - // Given an interrupt handler that sets an int32_t to 12345 - m_input.RequestInterrupts(InterruptHandler, ¶m); - m_input.EnableInterrupts(); + frc::AsynchronousInterrupt interrupt(m_input, + [&](auto a, auto b) { param = 12345; }); + interrupt.Enable(); // If the voltage rises m_output.Set(false); m_output.Set(true); - m_input.CancelInterrupts(); // Then the int32_t should be 12345 frc::Wait(kDelayTime); + + interrupt.Disable(); + EXPECT_EQ(12345, param) << "The interrupt did not run."; } -static void* InterruptTriggerer(void* data) { - auto& output = *static_cast(data); - output.Set(false); - frc::Wait(kSynchronousInterruptTime); - output.Set(true); - return nullptr; -} - TEST_F(DIOLoopTest, SynchronousInterruptWorks) { - // Given a synchronous interrupt - m_input.RequestInterrupts(); + Reset(); - // If we have another thread trigger the interrupt in a few seconds - pthread_t interruptTriggererLoop; - pthread_create(&interruptTriggererLoop, nullptr, InterruptTriggerer, - &m_output); + // Given a synchronous interrupt + frc::SynchronousInterrupt interrupt(m_input); + + std::thread thr([this]() { + m_output.Set(false); + frc::Wait(kSynchronousInterruptTime); + m_output.Set(true); + }); // Then this thread should pause and resume after that number of seconds frc::Timer timer; timer.Start(); - m_input.WaitForInterrupt(kSynchronousInterruptTime + 1_s); - EXPECT_NEAR_UNITS(kSynchronousInterruptTime, timer.Get(), - kSynchronousInterruptTimeTolerance); + interrupt.WaitForInterrupt(kSynchronousInterruptTime + 1_s); + auto time = timer.Get().to(); + if (thr.joinable()) + thr.join(); + EXPECT_NEAR(kSynchronousInterruptTime.to(), time, + kSynchronousInterruptTimeTolerance.to()); } diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/AsynchronousInterrupt.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/AsynchronousInterrupt.java new file mode 100644 index 0000000000..bd261f1a20 --- /dev/null +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/AsynchronousInterrupt.java @@ -0,0 +1,136 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package edu.wpi.first.wpilibj; + +import static edu.wpi.first.wpilibj.util.ErrorMessages.requireNonNullParam; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; + +/** + * Class for handling asynchrounous interrupts. + * + *

By default, interrupts will occur on rising edge. Callbacks are disabled by default, and + * Enable() must be called before they will occur. + * + *

Both rising and falling edge can be indiciated if both a rising and falling happen between + * callbacks. + * + *

Synchronous interrupts are handled by the SynchronousInterrupt class. + */ +public class AsynchronousInterrupt implements AutoCloseable { + private final BiConsumer m_callback; + private final SynchronousInterrupt m_interrupt; + + private final AtomicBoolean m_keepRunning = new AtomicBoolean(false); + private Thread m_thread; + + /** + * Construct a new asynchronous interrupt using a Digital Source. + * + *

At construction, the interrupt will trigger on the rising edge. + * + *

Callbacks will not be triggered until enable() is called. + * + * @param source The digital source to use. + * @param callback The callback to call on an interrupt + */ + public AsynchronousInterrupt(DigitalSource source, BiConsumer callback) { + m_callback = requireNonNullParam(callback, "callback", "AsynchronousInterrupt"); + m_interrupt = new SynchronousInterrupt(source); + } + + /** + * Closes the interrupt. + * + *

This does not close the associated digital source. + * + *

This will disable the interrupt if it is enabled. + */ + @Override + public void close() { + disable(); + m_interrupt.close(); + } + + /** + * Enables interrupt callbacks. Before this, callbacks will not occur. Does nothing if already + * enabled. + */ + public void enable() { + if (m_keepRunning.get()) { + return; + } + + m_keepRunning.set(true); + m_thread = new Thread(this::threadMain); + } + + /** Disables interrupt callbacks. Does nothing if already disabled. */ + public void disable() { + m_keepRunning.set(false); + m_interrupt.wakeupWaitingInterrupt(); + if (m_thread != null) { + if (m_thread.isAlive()) { + try { + m_thread.interrupt(); + m_thread.join(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + m_thread = null; + } + } + + /** + * Set which edges to trigger the interrupt on. + * + * @param risingEdge Trigger on rising edge + * @param fallingEdge Trigger on falling edge + */ + public void setInterruptEdges(boolean risingEdge, boolean fallingEdge) { + m_interrupt.setInterruptEdges(risingEdge, fallingEdge); + } + + /** + * Get the timestamp of the last rising edge. + * + *

This function does not require the interrupt to be enabled to work. + * + *

This only works if rising edge was configured using setInterruptEdges. + * + * @return the timestamp in seconds relative to getFPGATime + */ + public double getRisingTimestamp() { + return m_interrupt.getRisingTimestamp(); + } + + /** + * Get the timestamp of the last falling edge. + * + *

This function does not require the interrupt to be enabled to work. + * + *

This only works if falling edge was configured using setInterruptEdges. + * + * @return the timestamp in seconds relative to getFPGATime + */ + public double getFallingTimestamp() { + return m_interrupt.getFallingTimestamp(); + } + + private void threadMain() { + while (m_keepRunning.get()) { + var result = m_interrupt.waitForInterruptRaw(10, false); + if (!m_keepRunning.get()) { + break; + } + if (result == 0) { + continue; + } + m_callback.accept((result & 0x1) != 0, (result & 0x100) != 0); + } + } +} diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/DigitalInput.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/DigitalInput.java index 867688fa06..c74ef3c1a2 100644 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/DigitalInput.java +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/DigitalInput.java @@ -40,9 +40,6 @@ public class DigitalInput extends DigitalSource implements Sendable { public void close() { super.close(); SendableRegistry.remove(this); - if (m_interrupt != 0) { - cancelInterrupts(); - } DIOJNI.freeDIOPort(m_handle); m_handle = 0; } diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/DigitalSource.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/DigitalSource.java index 260417024d..cb8b02475b 100644 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/DigitalSource.java +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/DigitalSource.java @@ -10,8 +10,25 @@ package edu.wpi.first.wpilibj; * just provides a channel, then a digital input will be constructed and freed when finished for the * source. The source can either be a digital input or analog trigger but not both. */ -public abstract class DigitalSource extends InterruptableSensorBase { +public abstract class DigitalSource implements AutoCloseable { public abstract boolean isAnalogTrigger(); public abstract int getChannel(); + + /** + * If this is an analog trigger. + * + * @return true if this is an analog trigger. + */ + public abstract int getAnalogTriggerTypeForRouting(); + + /** + * The channel routing number. + * + * @return channel routing number + */ + public abstract int getPortHandleForRouting(); + + @Override + public void close() {} } diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/InterruptHandlerFunction.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/InterruptHandlerFunction.java deleted file mode 100644 index 4f3fc1f992..0000000000 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/InterruptHandlerFunction.java +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) FIRST and other WPILib contributors. -// Open Source Software; you can modify and/or share it under the terms of -// the WPILib BSD license file in the root directory of this project. - -package edu.wpi.first.wpilibj; - -import edu.wpi.first.hal.InterruptJNI.InterruptJNIHandlerFunction; - -/** - * It is recommended that you use this class in conjunction with classes from {@link - * java.util.concurrent.atomic} as these objects are all thread safe. - * - * @param 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. - */ - private class Function implements InterruptJNIHandlerFunction { - @SuppressWarnings("unchecked") - @Override - public void apply(int interruptAssertedMask, Object param) { - interruptFired(interruptAssertedMask, (T) param); - } - } - - final Function m_function = new Function(); - - /** - * This method is run every time an interrupt is fired. - * - * @param interruptAssertedMask Interrupt Mask - * @param param The parameter provided by overriding the {@link #overridableParameter()} method. - */ - public abstract void interruptFired(int interruptAssertedMask, T param); - - /** - * Override this method if you would like to pass a specific parameter to the {@link - * #interruptFired(int, Object)} when it is fired by the interrupt. This method is called once - * when {@link InterruptableSensorBase#requestInterrupts(InterruptHandlerFunction)} is run. - * - * @return The object that should be passed to the interrupt when it runs - */ - public T overridableParameter() { - return null; - } -} diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/InterruptableSensorBase.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/InterruptableSensorBase.java deleted file mode 100644 index 2a345ad0dd..0000000000 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/InterruptableSensorBase.java +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright (c) FIRST and other WPILib contributors. -// Open Source Software; you can modify and/or share it under the terms of -// the WPILib BSD license file in the root directory of this project. - -package edu.wpi.first.wpilibj; - -import edu.wpi.first.hal.InterruptJNI; -import edu.wpi.first.hal.util.AllocationException; -import java.util.function.Consumer; - -/** Base for sensors to be used with interrupts. */ -public abstract class InterruptableSensorBase implements AutoCloseable { - @SuppressWarnings("MissingJavadocMethod") - public enum WaitResult { - kTimeout(0x0), - kRisingEdge(0x1), - kFallingEdge(0x100), - kBoth(0x101); - - public final int value; - - WaitResult(int value) { - this.value = value; - } - - public static WaitResult getValue(boolean rising, boolean falling) { - if (rising && falling) { - return kBoth; - } else if (rising) { - return kRisingEdge; - } else if (falling) { - return kFallingEdge; - } else { - return kTimeout; - } - } - } - - /** The interrupt resource. */ - protected int m_interrupt = InterruptJNI.HalInvalidHandle; - - /** Flags if the interrupt being allocated is synchronous. */ - protected boolean m_isSynchronousInterrupt; - - /** Create a new InterrupatableSensorBase. */ - public InterruptableSensorBase() { - m_interrupt = 0; - } - - @Override - public void close() { - if (m_interrupt != 0) { - cancelInterrupts(); - } - } - - /** - * If this is an analog trigger. - * - * @return true if this is an analog trigger. - */ - public abstract int getAnalogTriggerTypeForRouting(); - - /** - * The channel routing number. - * - * @return channel routing number - */ - public abstract int getPortHandleForRouting(); - - /** - * Request one of the 8 interrupts asynchronously on this digital input. - * - * @param handler The {@link Consumer} that will be called whenever there is an interrupt on this - * device. Request interrupts in synchronous mode where the user program interrupt handler - * will be called when an interrupt occurs. The default is interrupt on rising edges only. - */ - public void requestInterrupts(Consumer handler) { - if (m_interrupt != 0) { - throw new AllocationException("The interrupt has already been allocated"); - } - - allocateInterrupts(false); - - assert m_interrupt != 0; - - InterruptJNI.requestInterrupts( - m_interrupt, getPortHandleForRouting(), getAnalogTriggerTypeForRouting()); - setUpSourceEdge(true, false); - InterruptJNI.attachInterruptHandler( - m_interrupt, - (mask, obj) -> { - // Rising edge result is the interrupt bit set in the byte 0xFF - // Falling edge result is the interrupt bit set in the byte 0xFF00 - boolean rising = (mask & 0xFF) != 0; - boolean falling = (mask & 0xFF00) != 0; - handler.accept(WaitResult.getValue(rising, falling)); - }, - null); - } - - /** - * Request one of the 8 interrupts asynchronously on this digital input. - * - * @param handler The {@link InterruptHandlerFunction} that contains the method {@link - * InterruptHandlerFunction#interruptFired(int, Object)} that will be called whenever there is - * an interrupt on this device. Request interrupts in synchronous mode where the user program - * interrupt handler will be called when an interrupt occurs. The default is interrupt on - * rising edges only. - */ - public void requestInterrupts(InterruptHandlerFunction handler) { - if (m_interrupt != 0) { - throw new AllocationException("The interrupt has already been allocated"); - } - - allocateInterrupts(false); - - assert m_interrupt != 0; - - InterruptJNI.requestInterrupts( - m_interrupt, getPortHandleForRouting(), getAnalogTriggerTypeForRouting()); - setUpSourceEdge(true, false); - InterruptJNI.attachInterruptHandler( - m_interrupt, handler.m_function, handler.overridableParameter()); - } - - /** - * Request one of the 8 interrupts synchronously on this digital input. Request interrupts in - * synchronous mode where the user program will have to explicitly wait for the interrupt to occur - * using {@link #waitForInterrupt}. The default is interrupt on rising edges only. - */ - public void requestInterrupts() { - if (m_interrupt != 0) { - throw new AllocationException("The interrupt has already been allocated"); - } - - allocateInterrupts(true); - - assert m_interrupt != 0; - - InterruptJNI.requestInterrupts( - m_interrupt, getPortHandleForRouting(), getAnalogTriggerTypeForRouting()); - setUpSourceEdge(true, false); - } - - /** - * Allocate the interrupt. - * - * @param watcher true if the interrupt should be in synchronous mode where the user program will - * have to explicitly wait for the interrupt to occur. - */ - protected void allocateInterrupts(boolean watcher) { - m_isSynchronousInterrupt = watcher; - - m_interrupt = InterruptJNI.initializeInterrupts(watcher); - } - - /** - * Cancel interrupts on this device. This deallocates all the chipobject structures and disables - * any interrupts. - */ - public void cancelInterrupts() { - if (m_interrupt == 0) { - throw new IllegalStateException("The interrupt is not allocated."); - } - InterruptJNI.cleanInterrupts(m_interrupt); - m_interrupt = 0; - } - - /** - * In synchronous mode, wait for the defined interrupt to occur. - * - * @param timeout Timeout in seconds - * @param ignorePrevious If true, ignore interrupts that happened before waitForInterrupt was - * called. - * @return Result of the wait. - */ - public WaitResult waitForInterrupt(double timeout, boolean ignorePrevious) { - if (m_interrupt == 0) { - throw new IllegalStateException("The interrupt is not allocated."); - } - int result = InterruptJNI.waitForInterrupt(m_interrupt, timeout, ignorePrevious); - - // Rising edge result is the interrupt bit set in the byte 0xFF - // Falling edge result is the interrupt bit set in the byte 0xFF00 - // Set any bit set to be true for that edge, and AND the 2 results - // together to match the existing enum for all interrupts - boolean rising = (result & 0xFF) != 0; - boolean falling = (result & 0xFF00) != 0; - return WaitResult.getValue(rising, falling); - } - - /** - * In synchronous mode, wait for the defined interrupt to occur. - * - * @param timeout Timeout in seconds - * @return Result of the wait. - */ - public WaitResult waitForInterrupt(double timeout) { - return waitForInterrupt(timeout, true); - } - - /** - * Enable interrupts to occur on this input. Interrupts are disabled when the RequestInterrupt - * call is made. This gives time to do the setup of the other options before starting to field - * interrupts. - */ - public void enableInterrupts() { - if (m_interrupt == 0) { - throw new IllegalStateException("The interrupt is not allocated."); - } - if (m_isSynchronousInterrupt) { - throw new IllegalStateException("You do not need to enable synchronous interrupts"); - } - InterruptJNI.enableInterrupts(m_interrupt); - } - - /** Disable Interrupts without without deallocating structures. */ - public void disableInterrupts() { - if (m_interrupt == 0) { - throw new IllegalStateException("The interrupt is not allocated."); - } - if (m_isSynchronousInterrupt) { - throw new IllegalStateException("You can not disable synchronous interrupts"); - } - InterruptJNI.disableInterrupts(m_interrupt); - } - - /** - * Return the timestamp for the rising interrupt that occurred most recently. This is in the same - * time domain as getClock(). The rising-edge interrupt should be enabled with {@link - * #setUpSourceEdge}. - * - * @return Timestamp in seconds since boot. - */ - public double readRisingTimestamp() { - if (m_interrupt == 0) { - throw new IllegalStateException("The interrupt is not allocated."); - } - return InterruptJNI.readInterruptRisingTimestamp(m_interrupt) * 1e-6; - } - - /** - * Return the timestamp for the falling interrupt that occurred most recently. This is in the same - * time domain as getClock(). The falling-edge interrupt should be enabled with {@link - * #setUpSourceEdge}. - * - * @return Timestamp in seconds since boot. - */ - public double readFallingTimestamp() { - if (m_interrupt == 0) { - throw new IllegalStateException("The interrupt is not allocated."); - } - return InterruptJNI.readInterruptFallingTimestamp(m_interrupt) * 1e-6; - } - - /** - * Set which edge to trigger interrupts on. - * - * @param risingEdge true to interrupt on rising edge - * @param fallingEdge true to interrupt on falling edge - */ - public void setUpSourceEdge(boolean risingEdge, boolean fallingEdge) { - if (m_interrupt != 0) { - InterruptJNI.setInterruptUpSourceEdge(m_interrupt, risingEdge, fallingEdge); - } else { - throw new IllegalArgumentException("You must call RequestInterrupts before setUpSourceEdge"); - } - } -} diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/SynchronousInterrupt.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/SynchronousInterrupt.java new file mode 100644 index 0000000000..5033c609a9 --- /dev/null +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/SynchronousInterrupt.java @@ -0,0 +1,159 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package edu.wpi.first.wpilibj; + +import static edu.wpi.first.wpilibj.util.ErrorMessages.requireNonNullParam; + +import edu.wpi.first.hal.InterruptJNI; + +/** + * Class for handling sychronous interrupts. + * + *

By default, interrupts will occur on rising edge. + * + *

Asynchronous interrupts are handled by the AsynchronousInterrupt class. + */ +public class SynchronousInterrupt implements AutoCloseable { + @SuppressWarnings("PMD.SingularField") + private final DigitalSource m_source; + + private final int m_handle; + + @SuppressWarnings("JavadocMethod") + public enum WaitResult { + kTimeout(0x0), + kRisingEdge(0x1), + kFallingEdge(0x100), + kBoth(0x101); + + @SuppressWarnings("MemberName") + public final int value; + + WaitResult(int value) { + this.value = value; + } + + /** Create a wait result. */ + public static WaitResult getValue(boolean rising, boolean falling) { + if (rising && falling) { + return kBoth; + } else if (rising) { + return kRisingEdge; + } else if (falling) { + return kFallingEdge; + } else { + return kTimeout; + } + } + } + + /** + * Constructs a new synchronous interrupt using a DigitalSource. + * + *

At construction, the interrupt will trigger on the rising edge. + * + * @param source The digital source to use. + */ + public SynchronousInterrupt(DigitalSource source) { + m_source = requireNonNullParam(source, "source", "SynchronousInterrupt"); + m_handle = InterruptJNI.initializeInterrupts(); + InterruptJNI.requestInterrupts( + m_handle, m_source.getPortHandleForRouting(), m_source.getAnalogTriggerTypeForRouting()); + InterruptJNI.setInterruptUpSourceEdge(m_handle, true, false); + } + + /** + * Closes the interrupt. + * + *

This does not close the associated digital source. + */ + @Override + public void close() { + InterruptJNI.cleanInterrupts(m_handle); + } + + /** + * Wait for interrupt that returns the raw result value from the hardware. + * + *

Used by AsynchronousInterrupt. Users prefer to use waitForInterrupt. + * + * @param timeoutSeconds The timeout in seconds. 0 or less will return immediately. + * @param ignorePrevious True to ignore if a previous interrupt has occured, and only wait for a + * new trigger. False will consider if an interrupt has occured since the last time the + * interrupt was read. + * @return The raw hardware interrupt result + */ + int waitForInterruptRaw(double timeoutSeconds, boolean ignorePrevious) { + return InterruptJNI.waitForInterrupt(m_handle, timeoutSeconds, ignorePrevious); + } + + /** + * Wait for an interrupt. + * + * @param timeoutSeconds The timeout in seconds. 0 or less will return immediately. + * @param ignorePrevious True to ignore if a previous interrupt has occured, and only wait for a + * new trigger. False will consider if an interrupt has occured since the last time the + * interrupt was read. + * @return Result of which edges were triggered, or if an timeout occured. + */ + public WaitResult waitForInterrupt(double timeoutSeconds, boolean ignorePrevious) { + int result = InterruptJNI.waitForInterrupt(m_handle, timeoutSeconds, ignorePrevious); + + // Rising edge result is the interrupt bit set in the byte 0xFF + // Falling edge result is the interrupt bit set in the byte 0xFF00 + // Set any bit set to be true for that edge, and AND the 2 results + // together to match the existing enum for all interrupts + boolean rising = (result & 0xFF) != 0; + boolean falling = (result & 0xFF00) != 0; + return WaitResult.getValue(rising, falling); + } + + /** + * Wait for an interrupt, ingoring any previously occuring interrupts. + * + * @param timeoutSeconds The timeout in seconds. 0 or less will return immediately. + * @return Result of which edges were triggered, or if an timeout occured. + */ + public WaitResult waitForInterrupt(double timeoutSeconds) { + return waitForInterrupt(timeoutSeconds, true); + } + + /** + * Set which edges to trigger the interrupt on. + * + * @param risingEdge Trigger on rising edge + * @param fallingEdge Trigger on falling edge + */ + public void setInterruptEdges(boolean risingEdge, boolean fallingEdge) { + InterruptJNI.setInterruptUpSourceEdge(m_handle, risingEdge, fallingEdge); + } + + /** + * Get the timestamp of the last rising edge. + * + *

This only works if rising edge was configured using setInterruptEdges. + * + * @return the timestamp in seconds relative to getFPGATime + */ + public double getRisingTimestamp() { + return InterruptJNI.readInterruptRisingTimestamp(m_handle) * 1e-6; + } + + /** + * Get the timestamp of the last falling edge. + * + *

This only works if falling edge was configured using setInterruptEdges. + * + * @return the timestamp in seconds relative to getFPGATime + */ + public double getFallingTimestamp() { + return InterruptJNI.readInterruptFallingTimestamp(m_handle) * 1e-6; + } + + /** Force triggering of any waiting interrupt, which will be seen as a timeout. */ + public void wakeupWaitingInterrupt() { + InterruptJNI.releaseWaitingInterrupt(m_handle); + } +} diff --git a/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/AbstractInterruptTest.java b/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/AbstractInterruptTest.java index 7a49566782..e9d8d84d8d 100644 --- a/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/AbstractInterruptTest.java +++ b/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/AbstractInterruptTest.java @@ -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 { - 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()); } } diff --git a/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/AnalogCrossConnectTest.java b/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/AnalogCrossConnectTest.java index 85e33270d7..34c57fb3fb 100644 --- a/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/AnalogCrossConnectTest.java +++ b/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/AnalogCrossConnectTest.java @@ -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(); diff --git a/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/DIOCrossConnectTest.java b/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/DIOCrossConnectTest.java index ac8f924b62..000a42de74 100644 --- a/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/DIOCrossConnectTest.java +++ b/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/DIOCrossConnectTest.java @@ -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 } diff --git a/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/fixtures/DIOCrossConnectFixture.java b/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/fixtures/DIOCrossConnectFixture.java index 3cb2f5cf11..7c8b6f07b7 100644 --- a/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/fixtures/DIOCrossConnectFixture.java +++ b/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/fixtures/DIOCrossConnectFixture.java @@ -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; }