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

View File

@@ -21,65 +21,14 @@
using namespace hal;
namespace {
// Safe thread to allow callbacks to run on their own thread
class InterruptThread : public wpi::SafeThread {
public:
void Main() override {
std::unique_lock lock(m_mutex);
while (m_active) {
m_cond.wait(lock, [&] { return !m_active || m_notify; });
if (!m_active) {
break;
}
m_notify = false;
HAL_InterruptHandlerFunction handler = m_handler;
uint32_t mask = m_mask;
void* param = m_param;
lock.unlock(); // don't hold mutex during callback execution
handler(mask, param);
lock.lock();
}
}
bool m_notify = false;
HAL_InterruptHandlerFunction m_handler;
void* m_param;
uint32_t m_mask;
};
class InterruptThreadOwner : public wpi::SafeThreadOwner<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 {
std::unique_ptr<tInterrupt> anInterrupt;
std::unique_ptr<tInterruptManager> manager;
std::unique_ptr<InterruptThreadOwner> threadOwner = nullptr;
void* param = nullptr;
};
} // namespace
static void threadedInterruptHandler(uint32_t mask, void* param) {
static_cast<InterruptThreadOwner*>(param)->Notify(mask);
}
static LimitedHandleResource<HAL_InterruptHandle, Interrupt, kNumInterrupts,
HAL_HandleEnum::Interrupt>* interruptHandles;
@@ -94,8 +43,7 @@ void InitializeInterrupts() {
extern "C" {
HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher,
int32_t* status) {
HAL_InterruptHandle HAL_InitializeInterrupts(int32_t* status) {
hal::init::CheckInit();
HAL_InterruptHandle handle = interruptHandles->Allocate();
if (handle == HAL_kInvalidHandle) {
@@ -108,24 +56,16 @@ HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher,
anInterrupt->anInterrupt.reset(tInterrupt::create(interruptIndex, status));
anInterrupt->anInterrupt->writeConfig_WaitForAck(false, status);
anInterrupt->manager = std::make_unique<tInterruptManager>(
(1u << interruptIndex) | (1u << (interruptIndex + 8u)), watcher, status);
(1u << interruptIndex) | (1u << (interruptIndex + 8u)), true, status);
return handle;
}
void* HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle,
int32_t* status) {
void HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle) {
auto anInterrupt = interruptHandles->Get(interruptHandle);
interruptHandles->Free(interruptHandle);
if (anInterrupt == nullptr) {
return nullptr;
return;
}
if (anInterrupt->manager->isEnabled(status)) {
anInterrupt->manager->disable(status);
}
void* param = anInterrupt->param;
return param;
}
int64_t HAL_WaitForInterrupt(HAL_InterruptHandle interruptHandle,
@@ -150,31 +90,6 @@ int64_t HAL_WaitForInterrupt(HAL_InterruptHandle interruptHandle,
return result;
}
void HAL_EnableInterrupts(HAL_InterruptHandle interruptHandle,
int32_t* status) {
auto anInterrupt = interruptHandles->Get(interruptHandle);
if (anInterrupt == nullptr) {
*status = HAL_HANDLE_ERROR;
return;
}
if (!anInterrupt->manager->isEnabled(status)) {
anInterrupt->manager->enable(status);
}
}
void HAL_DisableInterrupts(HAL_InterruptHandle interruptHandle,
int32_t* status) {
auto anInterrupt = interruptHandles->Get(interruptHandle);
if (anInterrupt == nullptr) {
*status = HAL_HANDLE_ERROR;
return;
}
if (anInterrupt->manager->isEnabled(status)) {
anInterrupt->manager->disable(status);
}
}
int64_t HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle,
int32_t* status) {
auto anInterrupt = interruptHandles->Get(interruptHandle);
@@ -223,40 +138,6 @@ void HAL_RequestInterrupts(HAL_InterruptHandle interruptHandle,
anInterrupt->anInterrupt->writeConfig_Source_Module(routingModule, status);
}
void HAL_AttachInterruptHandler(HAL_InterruptHandle interruptHandle,
HAL_InterruptHandlerFunction handler,
void* param, int32_t* status) {
auto anInterrupt = interruptHandles->Get(interruptHandle);
if (anInterrupt == nullptr) {
*status = HAL_HANDLE_ERROR;
return;
}
anInterrupt->manager->registerHandler(handler, param, status);
anInterrupt->param = param;
}
void HAL_AttachInterruptHandlerThreaded(HAL_InterruptHandle interrupt_handle,
HAL_InterruptHandlerFunction handler,
void* param, int32_t* status) {
auto anInterrupt = interruptHandles->Get(interrupt_handle);
if (anInterrupt == nullptr) {
*status = HAL_HANDLE_ERROR;
return;
}
anInterrupt->threadOwner = std::make_unique<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,
HAL_Bool risingEdge, HAL_Bool fallingEdge,
int32_t* status) {

View File

@@ -17,126 +17,19 @@
using namespace hal;
// Thread where callbacks are actually performed.
//
// JNI's AttachCurrentThread() creates a Java Thread object on every
// invocation, which is both time inefficient and causes issues with Eclipse
// (which tries to keep a thread list up-to-date and thus gets swamped).
//
// Instead, this class attaches just once. When a hardware notification
// occurs, a condition variable wakes up this thread and this thread actually
// makes the call into Java.
//
// We don't want to use a FIFO here. If the user code takes too long to
// process, we will just ignore the redundant wakeup.
class InterruptThreadJNI : public wpi::SafeThread {
public:
void Main() override;
bool m_notify = false;
uint32_t m_mask = 0;
jobject m_func = nullptr;
jmethodID m_mid;
jobject m_param = nullptr;
};
class InterruptJNI : public wpi::SafeThreadOwner<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" {
/*
* Class: edu_wpi_first_hal_InterruptJNI
* Method: initializeInterrupts
* Signature: (Z)I
* Signature: ()I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_InterruptJNI_initializeInterrupts
(JNIEnv* env, jclass, jboolean watcher)
(JNIEnv* env, jclass)
{
int32_t status = 0;
HAL_InterruptHandle interrupt = HAL_InitializeInterrupts(watcher, &status);
HAL_InterruptHandle interrupt = HAL_InitializeInterrupts(&status);
CheckStatusForceThrow(env, status);
return (jint)interrupt;
@@ -151,14 +44,7 @@ JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_InterruptJNI_cleanInterrupts
(JNIEnv* env, jclass, jint interruptHandle)
{
int32_t status = 0;
auto param =
HAL_CleanInterrupts((HAL_InterruptHandle)interruptHandle, &status);
if (param) {
delete static_cast<InterruptJNI*>(param);
}
// ignore status, as an invalid handle just needs to be ignored.
HAL_CleanInterrupts((HAL_InterruptHandle)interruptHandle);
}
/*
@@ -179,36 +65,6 @@ Java_edu_wpi_first_hal_InterruptJNI_waitForInterrupt
return result;
}
/*
* Class: edu_wpi_first_hal_InterruptJNI
* Method: enableInterrupts
* Signature: (I)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_InterruptJNI_enableInterrupts
(JNIEnv* env, jclass, jint interruptHandle)
{
int32_t status = 0;
HAL_EnableInterrupts((HAL_InterruptHandle)interruptHandle, &status);
CheckStatus(env, status);
}
/*
* Class: edu_wpi_first_hal_InterruptJNI
* Method: disableInterrupts
* Signature: (I)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_InterruptJNI_disableInterrupts
(JNIEnv* env, jclass, jint interruptHandle)
{
int32_t status = 0;
HAL_DisableInterrupts((HAL_InterruptHandle)interruptHandle, &status);
CheckStatus(env, status);
}
/*
* Class: edu_wpi_first_hal_InterruptJNI
* Method: readInterruptRisingTimestamp
@@ -261,37 +117,6 @@ Java_edu_wpi_first_hal_InterruptJNI_requestInterrupts
CheckStatus(env, status);
}
/*
* Class: edu_wpi_first_hal_InterruptJNI
* Method: attachInterruptHandler
* Signature: (ILjava/lang/Object;Ljava/lang/Object;)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_InterruptJNI_attachInterruptHandler
(JNIEnv* env, jclass, jint interruptHandle, jobject handler, jobject param)
{
jclass cls = env->GetObjectClass(handler);
if (cls == nullptr) {
assert(false);
return;
}
jmethodID mid = env->GetMethodID(cls, "apply", "(ILjava/lang/Object;)V");
if (mid == nullptr) {
assert(false);
return;
}
InterruptJNI* intr = new InterruptJNI;
intr->Start();
intr->SetFunc(env, handler, mid, param);
int32_t status = 0;
HAL_AttachInterruptHandler((HAL_InterruptHandle)interruptHandle,
interruptHandler, intr, &status);
CheckStatus(env, status);
}
/*
* Class: edu_wpi_first_hal_InterruptJNI
* Method: setInterruptUpSourceEdge

View File

@@ -19,25 +19,20 @@
extern "C" {
#endif
typedef void (*HAL_InterruptHandlerFunction)(uint32_t interruptAssertedMask,
void* param);
/**
* Initializes an interrupt.
*
* @param watcher true for synchronous interrupts, false for asynchronous
* @return the created interrupt handle
*/
HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher, int32_t* status);
HAL_InterruptHandle HAL_InitializeInterrupts(int32_t* status);
/**
* Frees an interrupt.
*
* @param interruptHandle the interrupt handle
* @return the param passed to the interrupt, or nullptr if one
* wasn't passed.
*/
void* HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle, int32_t* status);
void HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle);
/**
* In synchronous mode, waits for the defined interrupt to occur.
@@ -52,25 +47,6 @@ int64_t HAL_WaitForInterrupt(HAL_InterruptHandle interruptHandle,
double timeout, HAL_Bool ignorePrevious,
int32_t* status);
/**
* Enables interrupts to occur on this input.
*
* Interrupts are disabled when the RequestInterrupt call is made. This gives
* time to do the setup of the other options before starting to field
* interrupts.
*
* @param interruptHandle the interrupt handle
*/
void HAL_EnableInterrupts(HAL_InterruptHandle interruptHandle, int32_t* status);
/**
* Disables interrupts without without deallocating structures.
*
* @param interruptHandle the interrupt handle
*/
void HAL_DisableInterrupts(HAL_InterruptHandle interruptHandle,
int32_t* status);
/**
* Returns the timestamp for the rising interrupt that occurred most recently.
*
@@ -110,34 +86,6 @@ void HAL_RequestInterrupts(HAL_InterruptHandle interruptHandle,
HAL_AnalogTriggerType analogTriggerType,
int32_t* status);
/**
* Attaches an asynchronous interrupt handler to the interrupt.
*
* This interrupt gets called directly on the FPGA interrupt thread, so will
* block other interrupts while running.
*
* @param interruptHandle the interrupt handle
* @param handler the handler function for the interrupt to call
* @param param a parameter to be passed to the handler
*/
void HAL_AttachInterruptHandler(HAL_InterruptHandle interruptHandle,
HAL_InterruptHandlerFunction handler,
void* param, int32_t* status);
/**
* Attaches an asynchronous interrupt handler to the interrupt.
*
* This interrupt gets called on a thread specific to the interrupt, so will not
* block other interrupts.
*
* @param interruptHandle the interrupt handle
* @param handler the handler function for the interrupt to call
* @param param a parameter to be passed to the handler
*/
void HAL_AttachInterruptHandlerThreaded(HAL_InterruptHandle interruptHandle,
HAL_InterruptHandlerFunction handler,
void* param, int32_t* status);
/**
* Sets the edges to trigger the interrupt on.
*

View File

@@ -42,16 +42,12 @@ struct Interrupt {
HAL_Handle portHandle;
uint8_t index;
HAL_AnalogTriggerType trigType;
bool watcher;
int64_t risingTimestamp;
int64_t fallingTimestamp;
bool previousState;
bool fireOnUp;
bool fireOnDown;
int32_t callbackId;
void* callbackParam;
HAL_InterruptHandlerFunction callbackFunction;
};
struct SynchronousWaitData {
@@ -83,8 +79,7 @@ void InitializeInterrupts() {
} // namespace hal::init
extern "C" {
HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher,
int32_t* status) {
HAL_InterruptHandle HAL_InitializeInterrupts(int32_t* status) {
hal::init::CheckInit();
HAL_InterruptHandle handle = interruptHandles->Allocate();
if (handle == HAL_kInvalidHandle) {
@@ -100,19 +95,10 @@ HAL_InterruptHandle HAL_InitializeInterrupts(HAL_Bool watcher,
anInterrupt->index = getHandleIndex(handle);
anInterrupt->callbackId = -1;
anInterrupt->watcher = watcher;
return handle;
}
void* HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle,
int32_t* status) {
HAL_DisableInterrupts(interruptHandle, status);
auto anInterrupt = interruptHandles->Get(interruptHandle);
void HAL_CleanInterrupts(HAL_InterruptHandle interruptHandle) {
interruptHandles->Free(interruptHandle);
if (anInterrupt == nullptr) {
return nullptr;
}
return anInterrupt->callbackParam;
}
static void ProcessInterruptDigitalSynchronous(const char* name, void* param,
@@ -352,12 +338,6 @@ int64_t HAL_WaitForInterrupt(HAL_InterruptHandle interruptHandle,
return WaitResult::Timeout;
}
// Check to make sure we are actually an interrupt in synchronous mode
if (!interrupt->watcher) {
*status = NiFpga_Status_InvalidParameter;
return WaitResult::Timeout;
}
if (interrupt->isAnalog) {
return WaitForInterruptAnalog(interruptHandle, interrupt.get(), timeout,
ignorePrevious);
@@ -367,196 +347,6 @@ int64_t HAL_WaitForInterrupt(HAL_InterruptHandle interruptHandle,
}
}
static void ProcessInterruptDigitalAsynchronous(const char* name, void* param,
const struct HAL_Value* value) {
// void* is a HAL handle
// convert to uintptr_t first, then to handle
uintptr_t handleTmp = reinterpret_cast<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,
int32_t* status) {
auto interrupt = interruptHandles->Get(interruptHandle);
@@ -602,24 +392,6 @@ void HAL_RequestInterrupts(HAL_InterruptHandle interruptHandle,
interrupt->trigType = analogTriggerType;
interrupt->portHandle = digitalSourceHandle;
}
void HAL_AttachInterruptHandler(HAL_InterruptHandle interruptHandle,
HAL_InterruptHandlerFunction handler,
void* param, int32_t* status) {
auto interrupt = interruptHandles->Get(interruptHandle);
if (interrupt == nullptr) {
*status = HAL_HANDLE_ERROR;
return;
}
interrupt->callbackFunction = handler;
interrupt->callbackParam = param;
}
void HAL_AttachInterruptHandlerThreaded(HAL_InterruptHandle interruptHandle,
HAL_InterruptHandlerFunction handler,
void* param, int32_t* status) {
HAL_AttachInterruptHandler(interruptHandle, handler, param, status);
}
void HAL_SetInterruptUpSourceEdge(HAL_InterruptHandle interruptHandle,
HAL_Bool risingEdge, HAL_Bool fallingEdge,
@@ -636,6 +408,19 @@ void HAL_SetInterruptUpSourceEdge(HAL_InterruptHandle interruptHandle,
void HAL_ReleaseWaitingInterrupt(HAL_InterruptHandle interruptHandle,
int32_t* status) {
// Requires a fairly large rewrite to get working
auto interrupt = interruptHandles->Get(interruptHandle);
if (interrupt == nullptr) {
*status = HAL_HANDLE_ERROR;
return;
}
synchronousInterruptHandles->ForEach(
[interruptHandle](SynchronousWaitDataHandle handle,
SynchronousWaitData* data) {
if (data->interruptHandle == interruptHandle) {
data->waitPredicate = true;
data->waitCond.notify_all();
}
});
}
} // extern "C"

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
* {@link CommandGroup#setInterruptible(boolean) setInterruptable(false)} was called or if it is
* {@link CommandGroup#setInterruptible(boolean) setInterruptible(false)} was called or if it is
* currently running an uninterruptible command or child.
*
* @return whether or not this {@link CommandGroup} is interruptible.

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
* 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
*/

View File

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

View File

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

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

View File

@@ -10,8 +10,9 @@
#include <units/time.h>
#include "TestBench.h"
#include "frc/AsynchronousInterrupt.h"
#include "frc/Counter.h"
#include "frc/InterruptableSensorBase.h"
#include "frc/SynchronousInterrupt.h"
#include "frc/Timer.h"
#include "gtest/gtest.h"
@@ -71,10 +72,10 @@ TEST_F(DIOLoopTest, DIOPWM) {
m_output.EnablePWM(0.0);
frc::Wait(0.5_s);
m_output.UpdateDutyCycle(0.5);
m_input.RequestInterrupts();
m_input.SetUpSourceEdge(false, true);
frc::InterruptableSensorBase::WaitResult result =
m_input.WaitForInterrupt(3_s, true);
frc::SynchronousInterrupt interrupt{m_output};
interrupt.SetInterruptEdges(false, true);
frc::SynchronousInterrupt::WaitResult result =
interrupt.WaitForInterrupt(3_s, true);
frc::Wait(0.5_s);
bool firstCycle = m_input.Get();
@@ -96,7 +97,7 @@ TEST_F(DIOLoopTest, DIOPWM) {
frc::Wait(0.5_s);
bool secondAfterStop = m_input.Get();
EXPECT_EQ(frc::InterruptableSensorBase::WaitResult::kFallingEdge, result)
EXPECT_EQ(frc::SynchronousInterrupt::WaitResult::kFallingEdge, result)
<< "WaitForInterrupt was not falling.";
EXPECT_FALSE(firstCycle) << "Input not low after first delay";
@@ -132,48 +133,46 @@ TEST_F(DIOLoopTest, FakeCounter) {
EXPECT_EQ(100, counter.Get()) << "Counter did not count up to 100.";
}
static void InterruptHandler(uint32_t interruptAssertedMask, void* param) {
*reinterpret_cast<int32_t*>(param) = 12345;
}
TEST_F(DIOLoopTest, AsynchronousInterruptWorks) {
Reset();
int32_t param = 0;
// Given an interrupt handler that sets an int32_t to 12345
m_input.RequestInterrupts(InterruptHandler, &param);
m_input.EnableInterrupts();
frc::AsynchronousInterrupt interrupt(m_input,
[&](auto a, auto b) { param = 12345; });
interrupt.Enable();
// If the voltage rises
m_output.Set(false);
m_output.Set(true);
m_input.CancelInterrupts();
// Then the int32_t should be 12345
frc::Wait(kDelayTime);
interrupt.Disable();
EXPECT_EQ(12345, param) << "The interrupt did not run.";
}
static void* InterruptTriggerer(void* data) {
auto& output = *static_cast<frc::DigitalOutput*>(data);
output.Set(false);
frc::Wait(kSynchronousInterruptTime);
output.Set(true);
return nullptr;
}
TEST_F(DIOLoopTest, SynchronousInterruptWorks) {
// Given a synchronous interrupt
m_input.RequestInterrupts();
Reset();
// If we have another thread trigger the interrupt in a few seconds
pthread_t interruptTriggererLoop;
pthread_create(&interruptTriggererLoop, nullptr, InterruptTriggerer,
&m_output);
// Given a synchronous interrupt
frc::SynchronousInterrupt interrupt(m_input);
std::thread thr([this]() {
m_output.Set(false);
frc::Wait(kSynchronousInterruptTime);
m_output.Set(true);
});
// Then this thread should pause and resume after that number of seconds
frc::Timer timer;
timer.Start();
m_input.WaitForInterrupt(kSynchronousInterruptTime + 1_s);
EXPECT_NEAR_UNITS(kSynchronousInterruptTime, timer.Get(),
kSynchronousInterruptTimeTolerance);
interrupt.WaitForInterrupt(kSynchronousInterruptTime + 1_s);
auto time = timer.Get().to<double>();
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() {
super.close();
SendableRegistry.remove(this);
if (m_interrupt != 0) {
cancelInterrupts();
}
DIOJNI.freeDIOPort(m_handle);
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
* source. The source can either be a digital input or analog trigger but not both.
*/
public abstract class DigitalSource extends InterruptableSensorBase {
public abstract class DigitalSource implements AutoCloseable {
public abstract boolean isAnalogTrigger();
public abstract int getChannel();
/**
* If this is an analog trigger.
*
* @return true if this is an analog trigger.
*/
public abstract int getAnalogTriggerTypeForRouting();
/**
* The channel routing number.
*
* @return channel routing number
*/
public abstract int getPortHandleForRouting();
@Override
public void close() {}
}

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

View File

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

View File

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

View File

@@ -61,11 +61,6 @@ public class DIOCrossConnectFixture implements ITestFixture {
@Override
public boolean reset() {
try {
m_input.cancelInterrupts();
} catch (IllegalStateException ex) {
// This will happen if the interrupt has not been allocated for this test.
}
m_output.set(false);
return true;
}