[wpilib] Remove InterruptableSensorBase and replace with interrupt classes (#2410)

This commit is contained in:
Thad House
2021-06-05 11:25:21 -07:00
committed by GitHub
parent 15c521a7fe
commit 5c817082a0
28 changed files with 982 additions and 1497 deletions

View File

@@ -7,21 +7,13 @@ package edu.wpi.first.hal;
public class InterruptJNI extends JNIWrapper { public class InterruptJNI extends JNIWrapper {
public static final int HalInvalidHandle = 0; public static final int HalInvalidHandle = 0;
public interface InterruptJNIHandlerFunction { public static native int initializeInterrupts();
void apply(int interruptAssertedMask, Object param);
}
public static native int initializeInterrupts(boolean watcher);
public static native void cleanInterrupts(int interruptHandle); public static native void cleanInterrupts(int interruptHandle);
public static native int waitForInterrupt( public static native int waitForInterrupt(
int interruptHandle, double timeout, boolean ignorePrevious); 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 readInterruptRisingTimestamp(int interruptHandle);
public static native long readInterruptFallingTimestamp(int interruptHandle); public static native long readInterruptFallingTimestamp(int interruptHandle);
@@ -29,9 +21,6 @@ public class InterruptJNI extends JNIWrapper {
public static native void requestInterrupts( public static native void requestInterrupts(
int interruptHandle, int digitalSourceHandle, int analogTriggerType); int interruptHandle, int digitalSourceHandle, int analogTriggerType);
public static native void attachInterruptHandler(
int interruptHandle, InterruptJNIHandlerFunction handler, Object param);
public static native void setInterruptUpSourceEdge( public static native void setInterruptUpSourceEdge(
int interruptHandle, boolean risingEdge, boolean fallingEdge); int interruptHandle, boolean risingEdge, boolean fallingEdge);

View File

@@ -21,65 +21,14 @@
using namespace hal; using namespace hal;
namespace { 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<InterruptThread> {
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 { struct Interrupt {
std::unique_ptr<tInterrupt> anInterrupt; std::unique_ptr<tInterrupt> anInterrupt;
std::unique_ptr<tInterruptManager> manager; std::unique_ptr<tInterruptManager> manager;
std::unique_ptr<InterruptThreadOwner> threadOwner = nullptr;
void* param = nullptr;
}; };
} // namespace } // namespace
static void threadedInterruptHandler(uint32_t mask, void* param) {
static_cast<InterruptThreadOwner*>(param)->Notify(mask);
}
static LimitedHandleResource<HAL_InterruptHandle, Interrupt, kNumInterrupts, static LimitedHandleResource<HAL_InterruptHandle, Interrupt, kNumInterrupts,
HAL_HandleEnum::Interrupt>* interruptHandles; HAL_HandleEnum::Interrupt>* interruptHandles;
@@ -94,8 +43,7 @@ void InitializeInterrupts() {
extern "C" { extern "C" {
HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher, HAL_InterruptHandle HAL_InitializeInterrupts(int32_t* status) {
int32_t* status) {
hal::init::CheckInit(); hal::init::CheckInit();
HAL_InterruptHandle handle = interruptHandles->Allocate(); HAL_InterruptHandle handle = interruptHandles->Allocate();
if (handle == HAL_kInvalidHandle) { if (handle == HAL_kInvalidHandle) {
@@ -108,24 +56,16 @@ HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher,
anInterrupt->anInterrupt.reset(tInterrupt::create(interruptIndex, status)); anInterrupt->anInterrupt.reset(tInterrupt::create(interruptIndex, status));
anInterrupt->anInterrupt->writeConfig_WaitForAck(false, status); anInterrupt->anInterrupt->writeConfig_WaitForAck(false, status);
anInterrupt->manager = std::make_unique<tInterruptManager>( anInterrupt->manager = std::make_unique<tInterruptManager>(
(1u << interruptIndex) | (1u << (interruptIndex + 8u)), watcher, status); (1u << interruptIndex) | (1u << (interruptIndex + 8u)), true, status);
return handle; return handle;
} }
void* HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle, void HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle) {
int32_t* status) {
auto anInterrupt = interruptHandles->Get(interruptHandle); auto anInterrupt = interruptHandles->Get(interruptHandle);
interruptHandles->Free(interruptHandle); interruptHandles->Free(interruptHandle);
if (anInterrupt == nullptr) { 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, int64_t HAL_WaitForInterrupt(HAL_InterruptHandle interruptHandle,
@@ -150,31 +90,6 @@ int64_t HAL_WaitForInterrupt(HAL_InterruptHandle interruptHandle,
return result; 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, int64_t HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle,
int32_t* status) { int32_t* status) {
auto anInterrupt = interruptHandles->Get(interruptHandle); auto anInterrupt = interruptHandles->Get(interruptHandle);
@@ -223,40 +138,6 @@ void HAL_RequestInterrupts(HAL_InterruptHandle interruptHandle,
anInterrupt->anInterrupt->writeConfig_Source_Module(routingModule, status); 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<InterruptThreadOwner>();
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, void HAL_SetInterruptUpSourceEdge(HAL_InterruptHandle interruptHandle,
HAL_Bool risingEdge, HAL_Bool fallingEdge, HAL_Bool risingEdge, HAL_Bool fallingEdge,
int32_t* status) { int32_t* status) {

View File

@@ -17,126 +17,19 @@
using namespace hal; 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<InterruptThreadJNI> {
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<char*>("Interrupt");
args.group = nullptr;
jint rs = GetJVM()->AttachCurrentThreadAsDaemon(
reinterpret_cast<void**>(&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<jint>(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<InterruptJNI*>(param)->Notify(mask);
}
extern "C" { extern "C" {
/* /*
* Class: edu_wpi_first_hal_InterruptJNI * Class: edu_wpi_first_hal_InterruptJNI
* Method: initializeInterrupts * Method: initializeInterrupts
* Signature: (Z)I * Signature: ()I
*/ */
JNIEXPORT jint JNICALL JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_InterruptJNI_initializeInterrupts Java_edu_wpi_first_hal_InterruptJNI_initializeInterrupts
(JNIEnv* env, jclass, jboolean watcher) (JNIEnv* env, jclass)
{ {
int32_t status = 0; int32_t status = 0;
HAL_InterruptHandle interrupt = HAL_InitializeInterrupts(watcher, &status); HAL_InterruptHandle interrupt = HAL_InitializeInterrupts(&status);
CheckStatusForceThrow(env, status); CheckStatusForceThrow(env, status);
return (jint)interrupt; return (jint)interrupt;
@@ -151,14 +44,7 @@ JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_InterruptJNI_cleanInterrupts Java_edu_wpi_first_hal_InterruptJNI_cleanInterrupts
(JNIEnv* env, jclass, jint interruptHandle) (JNIEnv* env, jclass, jint interruptHandle)
{ {
int32_t status = 0; HAL_CleanInterrupts((HAL_InterruptHandle)interruptHandle);
auto param =
HAL_CleanInterrupts((HAL_InterruptHandle)interruptHandle, &status);
if (param) {
delete static_cast<InterruptJNI*>(param);
}
// ignore status, as an invalid handle just needs to be ignored.
} }
/* /*
@@ -179,36 +65,6 @@ Java_edu_wpi_first_hal_InterruptJNI_waitForInterrupt
return result; 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 * Class: edu_wpi_first_hal_InterruptJNI
* Method: readInterruptRisingTimestamp * Method: readInterruptRisingTimestamp
@@ -261,37 +117,6 @@ Java_edu_wpi_first_hal_InterruptJNI_requestInterrupts
CheckStatus(env, status); 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 * Class: edu_wpi_first_hal_InterruptJNI
* Method: setInterruptUpSourceEdge * Method: setInterruptUpSourceEdge

View File

@@ -19,25 +19,20 @@
extern "C" { extern "C" {
#endif #endif
typedef void (*HAL_InterruptHandlerFunction)(uint32_t interruptAssertedMask,
void* param);
/** /**
* Initializes an interrupt. * Initializes an interrupt.
* *
* @param watcher true for synchronous interrupts, false for asynchronous * @param watcher true for synchronous interrupts, false for asynchronous
* @return the created interrupt handle * @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. * Frees an interrupt.
* *
* @param interruptHandle the interrupt handle * @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. * 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, double timeout, HAL_Bool ignorePrevious,
int32_t* status); 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. * Returns the timestamp for the rising interrupt that occurred most recently.
* *
@@ -110,34 +86,6 @@ void HAL_RequestInterrupts(HAL_InterruptHandle interruptHandle,
HAL_AnalogTriggerType analogTriggerType, HAL_AnalogTriggerType analogTriggerType,
int32_t* status); 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. * Sets the edges to trigger the interrupt on.
* *

View File

@@ -42,16 +42,12 @@ struct Interrupt {
HAL_Handle portHandle; HAL_Handle portHandle;
uint8_t index; uint8_t index;
HAL_AnalogTriggerType trigType; HAL_AnalogTriggerType trigType;
bool watcher;
int64_t risingTimestamp; int64_t risingTimestamp;
int64_t fallingTimestamp; int64_t fallingTimestamp;
bool previousState; bool previousState;
bool fireOnUp; bool fireOnUp;
bool fireOnDown; bool fireOnDown;
int32_t callbackId; int32_t callbackId;
void* callbackParam;
HAL_InterruptHandlerFunction callbackFunction;
}; };
struct SynchronousWaitData { struct SynchronousWaitData {
@@ -83,8 +79,7 @@ void InitializeInterrupts() {
} // namespace hal::init } // namespace hal::init
extern "C" { extern "C" {
HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher, HAL_InterruptHandle HAL_InitializeInterrupts(int32_t* status) {
int32_t* status) {
hal::init::CheckInit(); hal::init::CheckInit();
HAL_InterruptHandle handle = interruptHandles->Allocate(); HAL_InterruptHandle handle = interruptHandles->Allocate();
if (handle == HAL_kInvalidHandle) { if (handle == HAL_kInvalidHandle) {
@@ -100,19 +95,10 @@ HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher,
anInterrupt->index = getHandleIndex(handle); anInterrupt->index = getHandleIndex(handle);
anInterrupt->callbackId = -1; anInterrupt->callbackId = -1;
anInterrupt->watcher = watcher;
return handle; return handle;
} }
void* HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle, void HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle) {
int32_t* status) {
HAL_DisableInterrupts(interruptHandle, status);
auto anInterrupt = interruptHandles->Get(interruptHandle);
interruptHandles->Free(interruptHandle); interruptHandles->Free(interruptHandle);
if (anInterrupt == nullptr) {
return nullptr;
}
return anInterrupt->callbackParam;
} }
static void ProcessInterruptDigitalSynchronous(const char* name, void* param, static void ProcessInterruptDigitalSynchronous(const char* name, void* param,
@@ -352,12 +338,6 @@ int64_t HAL_WaitForInterrupt(HAL_InterruptHandle interruptHandle,
return WaitResult::Timeout; 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) { if (interrupt->isAnalog) {
return WaitForInterruptAnalog(interruptHandle, interrupt.get(), timeout, return WaitForInterruptAnalog(interruptHandle, interrupt.get(), timeout,
ignorePrevious); 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<uintptr_t>(param);
HAL_InterruptHandle handle = static_cast<HAL_InterruptHandle>(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<uintptr_t>(param);
HAL_InterruptHandle handle = static_cast<HAL_InterruptHandle>(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<void*>(static_cast<uintptr_t>(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<void*>(static_cast<uintptr_t>(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, int64_t HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle,
int32_t* status) { int32_t* status) {
auto interrupt = interruptHandles->Get(interruptHandle); auto interrupt = interruptHandles->Get(interruptHandle);
@@ -602,24 +392,6 @@ void HAL_RequestInterrupts(HAL_InterruptHandle interruptHandle,
interrupt->trigType = analogTriggerType; interrupt->trigType = analogTriggerType;
interrupt->portHandle = digitalSourceHandle; 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, void HAL_SetInterruptUpSourceEdge(HAL_InterruptHandle interruptHandle,
HAL_Bool risingEdge, HAL_Bool fallingEdge, HAL_Bool risingEdge, HAL_Bool fallingEdge,
@@ -636,6 +408,19 @@ void HAL_SetInterruptUpSourceEdge(HAL_InterruptHandle interruptHandle,
void HAL_ReleaseWaitingInterrupt(HAL_InterruptHandle interruptHandle, void HAL_ReleaseWaitingInterrupt(HAL_InterruptHandle interruptHandle,
int32_t* status) { 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" } // extern "C"

View File

@@ -330,7 +330,7 @@ public class CommandGroup extends Command {
/** /**
* Returns whether or not this group is interruptible. A command group will be uninterruptible if * 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. * currently running an uninterruptible command or child.
* *
* @return whether or not this {@link CommandGroup} is interruptible. * @return whether or not this {@link CommandGroup} is interruptible.

View File

@@ -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 * 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 * Scheduler#run()} loop. Any command with conflicting requirements will be removed, unless it is
* uninterruptable. Giving <code>null</code> does nothing. * uninterruptible. Giving <code>null</code> does nothing.
* *
* @param command the {@link Command} to add * @param command the {@link Command} to add
*/ */

View File

@@ -6,6 +6,7 @@
#include <utility> #include <utility>
#include <hal/AnalogTrigger.h>
#include <hal/FRCUsageReporting.h> #include <hal/FRCUsageReporting.h>
#include <wpi/NullDeleter.h> #include <wpi/NullDeleter.h>

View File

@@ -4,9 +4,11 @@
#include "frc/AnalogTriggerOutput.h" #include "frc/AnalogTriggerOutput.h"
#include <hal/AnalogTrigger.h>
#include <hal/FRCUsageReporting.h> #include <hal/FRCUsageReporting.h>
#include "frc/AnalogTrigger.h" #include "frc/AnalogTrigger.h"
#include "frc/AnalogTriggerType.h"
#include "frc/Errors.h" #include "frc/Errors.h"
using namespace frc; using namespace frc;

View File

@@ -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 <frc/DigitalSource.h>
using namespace frc;
AsynchronousInterrupt::AsynchronousInterrupt(
DigitalSource& source, std::function<void(bool, bool)> callback)
: m_interrupt{source}, m_callback{std::move(callback)} {}
AsynchronousInterrupt::AsynchronousInterrupt(
DigitalSource* source, std::function<void(bool, bool)> callback)
: m_interrupt{source}, m_callback{std::move(callback)} {}
AsynchronousInterrupt::AsynchronousInterrupt(
std::shared_ptr<DigitalSource> source,
std::function<void(bool, bool)> 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();
}

View File

@@ -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<HAL_AnalogTriggerType>(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<InterruptEventHandler>(std::move(handler));
int32_t status = 0;
HAL_RequestInterrupts(
m_interrupt, GetPortHandleForRouting(),
static_cast<HAL_AnalogTriggerType>(GetAnalogTriggerTypeForRouting()),
&status);
SetUpSourceEdge(true, false);
HAL_AttachInterruptHandler(
m_interrupt,
[](uint32_t mask, void* param) {
auto self = reinterpret_cast<InterruptEventHandler*>(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<WaitResult>(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<HAL_AnalogTriggerType>(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<double>(),
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<WaitResult>(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");
}

View File

@@ -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 <type_traits>
#include <hal/Interrupts.h>
#include <wpi/NullDeleter.h>
#include "frc/DigitalSource.h"
#include "frc/Errors.h"
using namespace frc;
SynchronousInterrupt::SynchronousInterrupt(DigitalSource& source)
: m_source{&source, wpi::NullDeleter<DigitalSource>()} {
InitSynchronousInterrupt();
}
SynchronousInterrupt::SynchronousInterrupt(DigitalSource* source)
: m_source{source, wpi::NullDeleter<DigitalSource>()} {
if (m_source == nullptr) {
FRC_CheckErrorStatus(frc::err::NullParameter, "{}", "Source is null");
} else {
InitSynchronousInterrupt();
}
}
SynchronousInterrupt::SynchronousInterrupt(
std::shared_ptr<DigitalSource> 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<HAL_AnalogTriggerType>(
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<SynchronousInterrupt::WaitResult>;
return static_cast<SynchronousInterrupt::WaitResult>(static_cast<T>(lhs) |
static_cast<T>(rhs));
}
SynchronousInterrupt::WaitResult SynchronousInterrupt::WaitForInterrupt(
units::second_t timeout, bool ignorePrevious) {
int32_t status = 0;
auto result = HAL_WaitForInterrupt(m_handle, timeout.to<double>(),
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<double>(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<double>(ts)};
return ms;
}

View File

@@ -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 <frc/SynchronousInterrupt.h>
#include <atomic>
#include <functional>
#include <memory>
#include <thread>
#include <utility>
namespace frc {
/**
* Class for handling asynchronous interrupts.
*
* <p> By default, interrupts will occur on rising edge. Callbacks are disabled
* by default, and Enable() must be called before they will occur.
*
* <p> Both rising and falling edge can be indiciated if both a rising and
* falling happen between callbacks.
*
* <p>Synchronous interrupts are handled by the SynchronousInterrupt class.
*/
class AsynchronousInterrupt {
public:
/**
* Construct an Asynchronous Interrupt from a Digital Source.
*
* <p> At construction, the interrupt will trigger on the rising edge.
*
* <p> The first bool in the callback is rising, the 2nd is falling.
*/
AsynchronousInterrupt(DigitalSource& source,
std::function<void(bool, bool)> callback);
/**
* Construct an Asynchronous Interrupt from a Digital Source.
*
* <p> At construction, the interrupt will trigger on the rising edge.
*
* <p> The first bool in the callback is rising, the 2nd is falling.
*/
AsynchronousInterrupt(DigitalSource* source,
std::function<void(bool, bool)> callback);
/**
* Construct an Asynchronous Interrupt from a Digital Source.
*
* <p> At construction, the interrupt will trigger on the rising edge.
*
* <p> The first bool in the callback is rising, the 2nd is falling.
*/
AsynchronousInterrupt(std::shared_ptr<DigitalSource> source,
std::function<void(bool, bool)> callback);
/**
* Construct an Asynchronous Interrupt from a Digital Source.
*
* <p> At construction, the interrupt will trigger on the rising edge.
*
* <p> The first bool in the callback is rising, the 2nd is falling.
*/
template <typename Callable, typename Arg, typename... Args>
AsynchronousInterrupt(DigitalSource& source, Callable&& f, Arg&& arg,
Args&&... args)
: AsynchronousInterrupt(
source, std::bind(std::forward<Callable>(f), std::forward<Arg>(arg),
std::forward<Args>(args)...)) {}
/**
* Construct an Asynchronous Interrupt from a Digital Source.
*
* <p> At construction, the interrupt will trigger on the rising edge.
*
* <p> The first bool in the callback is rising, the 2nd is falling.
*/
template <typename Callable, typename Arg, typename... Args>
AsynchronousInterrupt(DigitalSource* source, Callable&& f, Arg&& arg,
Args&&... args)
: AsynchronousInterrupt(
source, std::bind(std::forward<Callable>(f), std::forward<Arg>(arg),
std::forward<Args>(args)...)) {}
/**
* Construct an Asynchronous Interrupt from a Digital Source.
*
* <p> At construction, the interrupt will trigger on the rising edge.
*
* <p> The first bool in the callback is rising, the 2nd is falling.
*/
template <typename Callable, typename Arg, typename... Args>
AsynchronousInterrupt(std::shared_ptr<DigitalSource> source, Callable&& f,
Arg&& arg, Args&&... args)
: AsynchronousInterrupt(
source, std::bind(std::forward<Callable>(f), std::forward<Arg>(arg),
std::forward<Args>(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.
*
* <p>This function does not require the interrupt to be enabled to work.
*
* <p>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.
*
* <p>This function does not require the interrupt to be enabled to work.
*
* <p>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<void(bool, bool)> m_callback;
};
} // namespace frc

View File

@@ -6,7 +6,7 @@
#include <hal/Types.h> #include <hal/Types.h>
#include "frc/InterruptableSensorBase.h" #include "frc/AnalogTriggerType.h"
namespace frc { namespace frc {
@@ -19,14 +19,14 @@ namespace frc {
* constructed and freed when finished for the source. The source can either be * constructed and freed when finished for the source. The source can either be
* a digital input or analog trigger but not both. * a digital input or analog trigger but not both.
*/ */
class DigitalSource : public InterruptableSensorBase { class DigitalSource {
public: public:
DigitalSource() = default; DigitalSource() = default;
DigitalSource(DigitalSource&&) = default; DigitalSource(DigitalSource&&) = default;
DigitalSource& operator=(DigitalSource&&) = default; DigitalSource& operator=(DigitalSource&&) = default;
HAL_Handle GetPortHandleForRouting() const override = 0; virtual HAL_Handle GetPortHandleForRouting() const = 0;
AnalogTriggerType GetAnalogTriggerTypeForRouting() const override = 0; virtual AnalogTriggerType GetAnalogTriggerTypeForRouting() const = 0;
virtual bool IsAnalogTrigger() const = 0; virtual bool IsAnalogTrigger() const = 0;
virtual int GetChannel() const = 0; virtual int GetChannel() const = 0;
}; };

View File

@@ -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 <functional>
#include <memory>
#include <hal/Interrupts.h>
#include <units/time.h>
#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<void(WaitResult)>;
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 <b>NOT</b> 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<HAL_InterruptHandle> m_interrupt;
std::unique_ptr<InterruptEventHandler> m_interruptHandler{nullptr};
void AllocateInterrupts(bool watcher);
};
} // namespace frc

View File

@@ -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 <memory>
#include <hal/Types.h>
#include <units/time.h>
namespace frc {
class DigitalSource;
/**
* Class for handling ssynchronous interrupts.
*
* <p> By default, interrupts will occur on rising edge.
*
* <p> 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<DigitalSource> source);
~SynchronousInterrupt();
SynchronousInterrupt(SynchronousInterrupt&&) = default;
SynchronousInterrupt& operator=(SynchronousInterrupt&&) = default;
/**
* Wait for an interrupt to occur.
*
* <p> 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.
*
* <p>This function does not require the interrupt to be enabled to work.
*
* <p>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<DigitalSource> m_source;
hal::Handle<HAL_InterruptHandle> m_handle;
};
} // namespace frc

View File

@@ -8,6 +8,7 @@
#include "frc/AnalogInput.h" #include "frc/AnalogInput.h"
#include "frc/AnalogOutput.h" #include "frc/AnalogOutput.h"
#include "frc/AnalogTrigger.h" #include "frc/AnalogTrigger.h"
#include "frc/AsynchronousInterrupt.h"
#include "frc/Counter.h" #include "frc/Counter.h"
#include "frc/Timer.h" #include "frc/Timer.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
@@ -91,10 +92,6 @@ TEST(AnalogLoopTest, AnalogTriggerCounterWorks) {
<< "Analog trigger counter did not count 50 ticks"; << "Analog trigger counter did not count 50 ticks";
} }
static void InterruptHandler(uint32_t interruptAssertedMask, void* param) {
*reinterpret_cast<int32_t*>(param) = 12345;
}
TEST(AnalogLoopTest, AsynchronusInterruptWorks) { TEST(AnalogLoopTest, AsynchronusInterruptWorks) {
frc::AnalogInput input{TestBench::kFakeAnalogOutputChannel}; frc::AnalogInput input{TestBench::kFakeAnalogOutputChannel};
frc::AnalogOutput output{TestBench::kAnalogOutputChannel}; frc::AnalogOutput output{TestBench::kAnalogOutputChannel};
@@ -106,16 +103,19 @@ TEST(AnalogLoopTest, AsynchronusInterruptWorks) {
// Given an interrupt handler that sets an int32_t to 12345 // Given an interrupt handler that sets an int32_t to 12345
std::shared_ptr<frc::AnalogTriggerOutput> triggerOutput = std::shared_ptr<frc::AnalogTriggerOutput> triggerOutput =
trigger.CreateOutput(frc::AnalogTriggerType::kState); trigger.CreateOutput(frc::AnalogTriggerType::kState);
triggerOutput->RequestInterrupts(InterruptHandler, &param);
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 // If the analog output moves from below to above the window
output.SetVoltage(0.0); output.SetVoltage(0.0);
frc::Wait(kDelayTime); frc::Wait(kDelayTime);
output.SetVoltage(5.0); output.SetVoltage(5.0);
triggerOutput->CancelInterrupts();
// Then the int32_t should be 12345 // Then the int32_t should be 12345
frc::Wait(kDelayTime); frc::Wait(kDelayTime);
interrupt.Disable();
EXPECT_EQ(12345, param) << "The interrupt did not run."; EXPECT_EQ(12345, param) << "The interrupt did not run.";
} }

View File

@@ -10,8 +10,9 @@
#include <units/time.h> #include <units/time.h>
#include "TestBench.h" #include "TestBench.h"
#include "frc/AsynchronousInterrupt.h"
#include "frc/Counter.h" #include "frc/Counter.h"
#include "frc/InterruptableSensorBase.h" #include "frc/SynchronousInterrupt.h"
#include "frc/Timer.h" #include "frc/Timer.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
@@ -71,10 +72,10 @@ TEST_F(DIOLoopTest, DIOPWM) {
m_output.EnablePWM(0.0); m_output.EnablePWM(0.0);
frc::Wait(0.5_s); frc::Wait(0.5_s);
m_output.UpdateDutyCycle(0.5); m_output.UpdateDutyCycle(0.5);
m_input.RequestInterrupts(); frc::SynchronousInterrupt interrupt{m_output};
m_input.SetUpSourceEdge(false, true); interrupt.SetInterruptEdges(false, true);
frc::InterruptableSensorBase::WaitResult result = frc::SynchronousInterrupt::WaitResult result =
m_input.WaitForInterrupt(3_s, true); interrupt.WaitForInterrupt(3_s, true);
frc::Wait(0.5_s); frc::Wait(0.5_s);
bool firstCycle = m_input.Get(); bool firstCycle = m_input.Get();
@@ -96,7 +97,7 @@ TEST_F(DIOLoopTest, DIOPWM) {
frc::Wait(0.5_s); frc::Wait(0.5_s);
bool secondAfterStop = m_input.Get(); bool secondAfterStop = m_input.Get();
EXPECT_EQ(frc::InterruptableSensorBase::WaitResult::kFallingEdge, result) EXPECT_EQ(frc::SynchronousInterrupt::WaitResult::kFallingEdge, result)
<< "WaitForInterrupt was not falling."; << "WaitForInterrupt was not falling.";
EXPECT_FALSE(firstCycle) << "Input not low after first delay"; 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."; EXPECT_EQ(100, counter.Get()) << "Counter did not count up to 100.";
} }
static void InterruptHandler(uint32_t interruptAssertedMask, void* param) {
*reinterpret_cast<int32_t*>(param) = 12345;
}
TEST_F(DIOLoopTest, AsynchronousInterruptWorks) { TEST_F(DIOLoopTest, AsynchronousInterruptWorks) {
Reset();
int32_t param = 0; int32_t param = 0;
// Given an interrupt handler that sets an int32_t to 12345 frc::AsynchronousInterrupt interrupt(m_input,
m_input.RequestInterrupts(InterruptHandler, &param); [&](auto a, auto b) { param = 12345; });
m_input.EnableInterrupts();
interrupt.Enable();
// If the voltage rises // If the voltage rises
m_output.Set(false); m_output.Set(false);
m_output.Set(true); m_output.Set(true);
m_input.CancelInterrupts();
// Then the int32_t should be 12345 // Then the int32_t should be 12345
frc::Wait(kDelayTime); frc::Wait(kDelayTime);
interrupt.Disable();
EXPECT_EQ(12345, param) << "The interrupt did not run."; EXPECT_EQ(12345, param) << "The interrupt did not run.";
} }
static void* InterruptTriggerer(void* data) {
auto& output = *static_cast<frc::DigitalOutput*>(data);
output.Set(false);
frc::Wait(kSynchronousInterruptTime);
output.Set(true);
return nullptr;
}
TEST_F(DIOLoopTest, SynchronousInterruptWorks) { TEST_F(DIOLoopTest, SynchronousInterruptWorks) {
// Given a synchronous interrupt Reset();
m_input.RequestInterrupts();
// If we have another thread trigger the interrupt in a few seconds // Given a synchronous interrupt
pthread_t interruptTriggererLoop; frc::SynchronousInterrupt interrupt(m_input);
pthread_create(&interruptTriggererLoop, nullptr, InterruptTriggerer,
&m_output); 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 // Then this thread should pause and resume after that number of seconds
frc::Timer timer; frc::Timer timer;
timer.Start(); timer.Start();
m_input.WaitForInterrupt(kSynchronousInterruptTime + 1_s); interrupt.WaitForInterrupt(kSynchronousInterruptTime + 1_s);
EXPECT_NEAR_UNITS(kSynchronousInterruptTime, timer.Get(), auto time = timer.Get().to<double>();
kSynchronousInterruptTimeTolerance); if (thr.joinable())
thr.join();
EXPECT_NEAR(kSynchronousInterruptTime.to<double>(), time,
kSynchronousInterruptTimeTolerance.to<double>());
} }

View File

@@ -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.
*
* <p>By default, interrupts will occur on rising edge. Callbacks are disabled by default, and
* Enable() must be called before they will occur.
*
* <p>Both rising and falling edge can be indiciated if both a rising and falling happen between
* callbacks.
*
* <p>Synchronous interrupts are handled by the SynchronousInterrupt class.
*/
public class AsynchronousInterrupt implements AutoCloseable {
private final BiConsumer<Boolean, Boolean> 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.
*
* <p>At construction, the interrupt will trigger on the rising edge.
*
* <p>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<Boolean, Boolean> callback) {
m_callback = requireNonNullParam(callback, "callback", "AsynchronousInterrupt");
m_interrupt = new SynchronousInterrupt(source);
}
/**
* Closes the interrupt.
*
* <p>This does not close the associated digital source.
*
* <p>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.
*
* <p>This function does not require the interrupt to be enabled to work.
*
* <p>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.
*
* <p>This function does not require the interrupt to be enabled to work.
*
* <p>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);
}
}
}

View File

@@ -40,9 +40,6 @@ public class DigitalInput extends DigitalSource implements Sendable {
public void close() { public void close() {
super.close(); super.close();
SendableRegistry.remove(this); SendableRegistry.remove(this);
if (m_interrupt != 0) {
cancelInterrupts();
}
DIOJNI.freeDIOPort(m_handle); DIOJNI.freeDIOPort(m_handle);
m_handle = 0; m_handle = 0;
} }

View File

@@ -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 * 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. * 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 boolean isAnalogTrigger();
public abstract int getChannel(); 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() {}
} }

View File

@@ -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 <T> The type of the parameter that should be returned to the the method {@link
* #interruptFired(int, Object)}
*/
public abstract class InterruptHandlerFunction<T> {
/**
* 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;
}
}

View File

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

View File

@@ -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.
*
* <p>By default, interrupts will occur on rising edge.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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);
}
}

View File

@@ -8,7 +8,6 @@ import static org.hamcrest.Matchers.both;
import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import edu.wpi.first.wpilibj.test.AbstractComsSetup; 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 * 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 { public abstract class AbstractInterruptTest extends AbstractComsSetup {
private InterruptableSensorBase m_interruptable = null; private DigitalSource m_source = null;
private InterruptableSensorBase getInterruptable() { private DigitalSource getSource() {
if (m_interruptable == null) { if (m_source == null) {
m_interruptable = giveInterruptableSensorBase(); m_source = giveSource();
} }
return m_interruptable; return m_source;
} }
@After @After
public void interruptTeardown() { public void interruptTeardown() {
if (m_interruptable != null) { if (m_source != null) {
freeInterruptableSensorBase(); freeSource();
m_interruptable = null; m_source = null;
} }
} }
/** Give the interruptable sensor base that interrupts can be attached to. */ /** Give the sensor source that interrupts can be attached to. */
abstract InterruptableSensorBase giveInterruptableSensorBase(); abstract DigitalSource giveSource();
/** /** Cleans up the sensor source. This is only called if {@link #giveSource()} is called. */
* Cleans up the interruptable sensor base. This is only called if {@link abstract void freeSource();
* #giveInterruptableSensorBase()} is called.
*/
abstract void freeInterruptableSensorBase();
/** Perform whatever action is required to set the interrupt high. */ /** Perform whatever action is required to set the interrupt high. */
abstract void setInterruptHigh(); abstract void setInterruptHigh();
@@ -55,116 +51,78 @@ public abstract class AbstractInterruptTest extends AbstractComsSetup {
/** Perform whatever action is required to set the interrupt low. */ /** Perform whatever action is required to set the interrupt low. */
abstract void setInterruptLow(); abstract void setInterruptLow();
private class InterruptCounter {
private final AtomicInteger m_count = new AtomicInteger();
void increment() {
m_count.addAndGet(1);
}
int getCount() {
return m_count.get();
}
}
private class TestInterruptHandlerFunction extends InterruptHandlerFunction<InterruptCounter> {
protected final AtomicBoolean m_exceptionThrown = new AtomicBoolean(false);
/** Stores the time that the interrupt fires. */
final AtomicLong m_interruptFireTime = new AtomicLong();
/** Stores if the interrupt has completed at least once. */
final AtomicBoolean m_interruptComplete = new AtomicBoolean(false);
protected Exception m_ex;
final InterruptCounter m_counter;
TestInterruptHandlerFunction(InterruptCounter counter) {
m_counter = counter;
}
@Override
public void interruptFired(int interruptAssertedMask, InterruptCounter param) {
m_interruptFireTime.set(RobotController.getFPGATime());
m_counter.increment();
try {
// This won't cause the test to fail
assertSame(m_counter, param);
} catch (Exception ex) {
// So we must throw the exception within the test
m_exceptionThrown.set(true);
m_ex = ex;
}
m_interruptComplete.set(true);
}
@Override
public InterruptCounter overridableParameter() {
return m_counter;
}
}
@Test(timeout = 1000) @Test(timeout = 1000)
public void testSingleInterruptsTriggering() throws Exception { public void testSingleInterruptsTriggering() throws Exception {
// Given // Given
final InterruptCounter counter = new InterruptCounter(); // final InterruptCounter counter = new InterruptCounter();
TestInterruptHandlerFunction function = new TestInterruptHandlerFunction(counter); // TestInterruptHandlerFunction function = new
// TestInterruptHandlerFunction(counter);
// When AtomicBoolean hasFired = new AtomicBoolean(false);
getInterruptable().requestInterrupts(function); AtomicInteger counter = new AtomicInteger(0);
getInterruptable().enableInterrupts(); AtomicLong interruptFireTime = new AtomicLong();
setInterruptLow(); try (AsynchronousInterrupt interrupt =
Timer.delay(0.01); new AsynchronousInterrupt(
// Note: Utility.getFPGATime() is used because double values can turn over getSource(),
// after the robot has been running for a long time (a, b) -> {
final long interruptTriggerTime = RobotController.getFPGATime(); interruptFireTime.set(RobotController.getFPGATime());
setInterruptHigh(); 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 assertEquals("The interrupt did not fire the expected number of times", 1, counter.get());
while (!function.m_interruptComplete.get()) {
Timer.delay(0.005); 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) @Test(timeout = 2000)
public void testMultipleInterruptsTriggering() { public void testMultipleInterruptsTriggering() {
// Given
final InterruptCounter counter = new InterruptCounter();
TestInterruptHandlerFunction function = new TestInterruptHandlerFunction(counter);
// When AtomicBoolean hasFired = new AtomicBoolean(false);
getInterruptable().requestInterrupts(function); AtomicInteger counter = new AtomicInteger(0);
getInterruptable().enableInterrupts();
final int fireCount = 50; try (AsynchronousInterrupt interrupt =
for (int i = 0; i < fireCount; i++) { new AsynchronousInterrupt(
setInterruptLow(); getSource(),
setInterruptHigh(); (a, b) -> {
// Wait for the interrupt to complete before moving on hasFired.set(true);
while (!function.m_interruptComplete.getAndSet(false)) { counter.incrementAndGet();
Timer.delay(0.005); })) {
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. */ /** The timeout length for this test in seconds. */
@@ -172,131 +130,128 @@ public abstract class AbstractInterruptTest extends AbstractComsSetup {
@Test(timeout = (long) (synchronousTimeout * 1e3)) @Test(timeout = (long) (synchronousTimeout * 1e3))
public void testSynchronousInterruptsTriggering() { public void testSynchronousInterruptsTriggering() {
// Given
getInterruptable().requestInterrupts();
final double synchronousDelay = synchronousTimeout / 2.0; try (SynchronousInterrupt interrupt = new SynchronousInterrupt(getSource())) {
final Runnable runnable = final double synchronousDelay = synchronousTimeout / 2.0;
() -> { final Runnable runnable =
Timer.delay(synchronousDelay); () -> {
setInterruptLow(); Timer.delay(synchronousDelay);
setInterruptHigh(); setInterruptLow();
}; setInterruptHigh();
};
// When // When
// Note: the long time value is used because doubles can flip if the robot // Note: the long time value is used because doubles can flip if the robot
// is left running for long enough // is left running for long enough
final long startTimeStamp = RobotController.getFPGATime(); final long startTimeStamp = RobotController.getFPGATime();
new Thread(runnable).start(); new Thread(runnable).start();
// Delay for twice as long as the timeout so the test should fail first // Delay for twice as long as the timeout so the test should fail first
getInterruptable().waitForInterrupt(synchronousTimeout * 2); interrupt.waitForInterrupt(synchronousTimeout * 2);
final long stopTimeStamp = RobotController.getFPGATime(); final long stopTimeStamp = RobotController.getFPGATime();
// Then // Then
// The test will not have timed out and: // The test will not have timed out and:
final double interruptRunTime = (stopTimeStamp - startTimeStamp) * 1e-6; final double interruptRunTime = (stopTimeStamp - startTimeStamp) * 1e-6;
assertEquals( assertEquals(
"The interrupt did not run for the expected amount of time (units in seconds)", "The interrupt did not run for the expected amount of time (units in seconds)",
synchronousDelay, synchronousDelay,
interruptRunTime, interruptRunTime,
0.1); 0.1);
}
} }
@Test(timeout = (long) (synchronousTimeout * 1e3)) @Test(timeout = (long) (synchronousTimeout * 1e3))
public void testSynchronousInterruptsWaitResultTimeout() { public void testSynchronousInterruptsWaitResultTimeout() {
// Given try (SynchronousInterrupt interrupt = new SynchronousInterrupt(getSource())) {
getInterruptable().requestInterrupts(); SynchronousInterrupt.WaitResult result = interrupt.waitForInterrupt(synchronousTimeout / 2);
// Don't fire interrupt. Expect it to timeout. assertEquals(
InterruptableSensorBase.WaitResult result = "The interrupt did not time out correctly.",
getInterruptable().waitForInterrupt(synchronousTimeout / 2); result,
SynchronousInterrupt.WaitResult.kTimeout);
assertEquals( }
"The interrupt did not time out correctly.",
result,
InterruptableSensorBase.WaitResult.kTimeout);
} }
@Test(timeout = (long) (synchronousTimeout * 1e3)) @Test(timeout = (long) (synchronousTimeout * 1e3))
public void testSynchronousInterruptsWaitResultRisingEdge() { public void testSynchronousInterruptsWaitResultRisingEdge() {
// Given try (SynchronousInterrupt interrupt = new SynchronousInterrupt(getSource())) {
getInterruptable().requestInterrupts();
final double synchronousDelay = synchronousTimeout / 2.0; final double synchronousDelay = synchronousTimeout / 2.0;
final Runnable runnable = final Runnable runnable =
() -> { () -> {
Timer.delay(synchronousDelay); Timer.delay(synchronousDelay);
setInterruptLow(); setInterruptLow();
setInterruptHigh(); setInterruptHigh();
}; };
new Thread(runnable).start(); new Thread(runnable).start();
// Delay for twice as long as the timeout so the test should fail first // Delay for twice as long as the timeout so the test should fail first
InterruptableSensorBase.WaitResult result = SynchronousInterrupt.WaitResult result = interrupt.waitForInterrupt(synchronousTimeout * 2);
getInterruptable().waitForInterrupt(synchronousTimeout * 2);
assertEquals( assertEquals(
"The interrupt did not fire on the rising edge.", "The interrupt did not fire on the rising edge.",
result, result,
InterruptableSensorBase.WaitResult.kRisingEdge); SynchronousInterrupt.WaitResult.kRisingEdge);
}
} }
@Test(timeout = (long) (synchronousTimeout * 1e3)) @Test(timeout = (long) (synchronousTimeout * 1e3))
public void testSynchronousInterruptsWaitResultFallingEdge() { public void testSynchronousInterruptsWaitResultFallingEdge() {
// Given try (SynchronousInterrupt interrupt = new SynchronousInterrupt(getSource())) {
getInterruptable().requestInterrupts(); // Given
getInterruptable().setUpSourceEdge(false, true); interrupt.setInterruptEdges(false, true);
final double synchronousDelay = synchronousTimeout / 2.0; final double synchronousDelay = synchronousTimeout / 2.0;
final Runnable runnable = final Runnable runnable =
() -> { () -> {
Timer.delay(synchronousDelay); Timer.delay(synchronousDelay);
setInterruptHigh(); setInterruptHigh();
setInterruptLow(); setInterruptLow();
}; };
new Thread(runnable).start(); new Thread(runnable).start();
// Delay for twice as long as the timeout so the test should fail first // Delay for twice as long as the timeout so the test should fail first
InterruptableSensorBase.WaitResult result = SynchronousInterrupt.WaitResult result = interrupt.waitForInterrupt(synchronousTimeout * 2);
getInterruptable().waitForInterrupt(synchronousTimeout * 2);
assertEquals( assertEquals(
"The interrupt did not fire on the falling edge.", "The interrupt did not fire on the falling edge.",
result, result,
InterruptableSensorBase.WaitResult.kFallingEdge); SynchronousInterrupt.WaitResult.kFallingEdge);
}
} }
@Test(timeout = 4000) @Test(timeout = 4000)
public void testDisableStopsInterruptFiring() { public void testDisableStopsInterruptFiring() {
final InterruptCounter counter = new InterruptCounter(); AtomicBoolean interruptComplete = new AtomicBoolean(false);
TestInterruptHandlerFunction function = new TestInterruptHandlerFunction(counter); AtomicInteger counter = new AtomicInteger(0);
try (AsynchronousInterrupt interrupt =
// When new AsynchronousInterrupt(
getInterruptable().requestInterrupts(function); getSource(),
getInterruptable().enableInterrupts(); (a, b) -> {
interruptComplete.set(true);
final int fireCount = 50; counter.incrementAndGet();
for (int i = 0; i < fireCount; i++) { })) {
setInterruptLow(); interrupt.enable();
setInterruptHigh(); final int fireCount = 50;
// Wait for the interrupt to complete before moving on for (int i = 0; i < fireCount; i++) {
while (!function.m_interruptComplete.getAndSet(false)) { 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); Timer.delay(0.005);
} }
}
getInterruptable().disableInterrupts();
// TestBench.out().println("Finished disabling the robot");
for (int i = 0; i < fireCount; i++) { assertEquals(
setInterruptLow(); "The interrupt did not fire the expected number of times", fireCount, counter.get());
setInterruptHigh();
// Just wait because the interrupt should not fire
Timer.delay(0.005);
} }
// Then
assertEquals(
"The interrupt did not fire the expected number of times", fireCount, counter.getCount());
} }
} }

View File

@@ -139,10 +139,10 @@ public class AnalogCrossConnectTest extends AbstractInterruptTest {
* (non-Javadoc) * (non-Javadoc)
* *
* @see * @see
* edu.wpi.first.wpilibj.AbstractInterruptTest#giveInterruptableSensorBase() * edu.wpi.first.wpilibj.AbstractInterruptTest#giveSource()
*/ */
@Override @Override
InterruptableSensorBase giveInterruptableSensorBase() { DigitalSource giveSource() {
m_interruptTrigger = new AnalogTrigger(analogIO.getInput()); m_interruptTrigger = new AnalogTrigger(analogIO.getInput());
m_interruptTrigger.setLimitsVoltage(2.0, 3.0); m_interruptTrigger.setLimitsVoltage(2.0, 3.0);
m_interruptTriggerOutput = m_interruptTriggerOutput =
@@ -154,11 +154,10 @@ public class AnalogCrossConnectTest extends AbstractInterruptTest {
* (non-Javadoc) * (non-Javadoc)
* *
* @see * @see
* edu.wpi.first.wpilibj.AbstractInterruptTest#freeInterruptableSensorBase() * edu.wpi.first.wpilibj.AbstractInterruptTest#freeSource()
*/ */
@Override @Override
void freeInterruptableSensorBase() { void freeSource() {
m_interruptTriggerOutput.cancelInterrupts();
m_interruptTriggerOutput.close(); m_interruptTriggerOutput.close();
m_interruptTriggerOutput = null; m_interruptTriggerOutput = null;
m_interruptTrigger.close(); m_interruptTrigger.close();

View File

@@ -101,10 +101,11 @@ public class DIOCrossConnectTest extends AbstractInterruptTest {
dio.getOutput().enablePWM(0.0); dio.getOutput().enablePWM(0.0);
Timer.delay(0.5); Timer.delay(0.5);
dio.getOutput().updateDutyCycle(0.5); dio.getOutput().updateDutyCycle(0.5);
dio.getInput().requestInterrupts(); try (var interruptHandler = new SynchronousInterrupt(dio.getInput())) {
dio.getInput().setUpSourceEdge(false, true); interruptHandler.setInterruptEdges(false, true);
// TODO: Add return value from WaitForInterrupt // TODO: Add return value from WaitForInterrupt
dio.getInput().waitForInterrupt(3.0, true); interruptHandler.waitForInterrupt(3.0, true);
}
Timer.delay(0.5); Timer.delay(0.5);
final boolean firstCycle = dio.getInput().get(); final boolean firstCycle = dio.getInput().get();
Timer.delay(0.5); Timer.delay(0.5);
@@ -140,10 +141,10 @@ public class DIOCrossConnectTest extends AbstractInterruptTest {
* (non-Javadoc) * (non-Javadoc)
* *
* @see * @see
* edu.wpi.first.wpilibj.AbstractInterruptTest#giveInterruptableSensorBase() * edu.wpi.first.wpilibj.AbstractInterruptTest#giveSource()
*/ */
@Override @Override
InterruptableSensorBase giveInterruptableSensorBase() { DigitalSource giveSource() {
return dio.getInput(); return dio.getInput();
} }
@@ -151,10 +152,10 @@ public class DIOCrossConnectTest extends AbstractInterruptTest {
* (non-Javadoc) * (non-Javadoc)
* *
* @see * @see
* edu.wpi.first.wpilibj.AbstractInterruptTest#freeInterruptableSensorBase() * edu.wpi.first.wpilibj.AbstractInterruptTest#freeSource()
*/ */
@Override @Override
void freeInterruptableSensorBase() { void freeSource() {
// Handled in the fixture // Handled in the fixture
} }

View File

@@ -61,11 +61,6 @@ public class DIOCrossConnectFixture implements ITestFixture {
@Override @Override
public boolean reset() { 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); m_output.set(false);
return true; return true;
} }