diff --git a/hal/src/main/native/athena/Solenoid.cpp b/hal/src/main/native/athena/Solenoid.cpp index f97c6f6089..d9a369e50d 100644 --- a/hal/src/main/native/athena/Solenoid.cpp +++ b/hal/src/main/native/athena/Solenoid.cpp @@ -154,4 +154,25 @@ void HAL_ClearAllPCMStickyFaults(int32_t module, int32_t* status) { *status = PCM_modules[module]->ClearStickyFaults(); } +void HAL_SetOneShotDuration(HAL_SolenoidHandle solenoidPortHandle, + int32_t durMS, int32_t* status) { + auto port = solenoidHandles.Get(solenoidPortHandle); + if (port == nullptr) { + *status = HAL_HANDLE_ERROR; + return; + } + + *status = + PCM_modules[port->module]->SetOneShotDurationMs(port->channel, durMS); +} + +void HAL_FireOneShot(HAL_SolenoidHandle solenoidPortHandle, int32_t* status) { + auto port = solenoidHandles.Get(solenoidPortHandle); + if (port == nullptr) { + *status = HAL_HANDLE_ERROR; + return; + } + + *status = PCM_modules[port->module]->FireOneShotSolenoid(port->channel); +} } // extern "C" diff --git a/hal/src/main/native/athena/ctre/PCM.cpp b/hal/src/main/native/athena/ctre/PCM.cpp index ab05212aca..08394084fe 100644 --- a/hal/src/main/native/athena/ctre/PCM.cpp +++ b/hal/src/main/native/athena/ctre/PCM.cpp @@ -213,7 +213,9 @@ CTR_Code PCM::FireOneShotSolenoid(UINT8 idx) return CTR_OKAY; } /* Configure the pulse width of a solenoid channel for one-shot pulse. - * Preprogrammed pulsewidth is 10ms resolute and can be between 20ms and 5.1s. + * Preprogrammed pulsewidth is 10ms resolution and can be between 10ms and + * 2.55s. + * * @Return - CTR_Code - Error code (if any) * @Param - idx - ID of solenoid [0,7] to configure. * @Param - durMs - pulse width in ms. diff --git a/hal/src/main/native/include/HAL/Solenoid.h b/hal/src/main/native/include/HAL/Solenoid.h index 3b5667309d..292c15ab82 100644 --- a/hal/src/main/native/include/HAL/Solenoid.h +++ b/hal/src/main/native/include/HAL/Solenoid.h @@ -30,6 +30,9 @@ int32_t HAL_GetPCMSolenoidBlackList(int32_t module, int32_t* status); HAL_Bool HAL_GetPCMSolenoidVoltageStickyFault(int32_t module, int32_t* status); HAL_Bool HAL_GetPCMSolenoidVoltageFault(int32_t module, int32_t* status); void HAL_ClearAllPCMStickyFaults(int32_t module, int32_t* status); +void HAL_SetOneShotDuration(HAL_SolenoidHandle solenoidPortHandle, + int32_t durMS, int32_t* status); +void HAL_FireOneShot(HAL_SolenoidHandle solenoidPortHandle, int32_t* status); #ifdef __cplusplus } // extern "C" #endif diff --git a/hal/src/main/native/sim/Solenoid.cpp b/hal/src/main/native/sim/Solenoid.cpp index 145bb75f67..0b377376ae 100644 --- a/hal/src/main/native/sim/Solenoid.cpp +++ b/hal/src/main/native/sim/Solenoid.cpp @@ -117,4 +117,7 @@ HAL_Bool HAL_GetPCMSolenoidVoltageFault(int32_t module, int32_t* status) { return 0; } void HAL_ClearAllPCMStickyFaults(int32_t module, int32_t* status) {} +void HAL_SetOneShotDuration(HAL_SolenoidHandle solenoidPortHandle, + int32_t durMS, int32_t* status) {} +void HAL_FireOneShot(HAL_SolenoidHandle solenoidPortHandle, int32_t* status) {} } // extern "C" diff --git a/wpilibc/src/main/native/cpp/Solenoid.cpp b/wpilibc/src/main/native/cpp/Solenoid.cpp index 03003280d5..ef5c9d0b59 100644 --- a/wpilibc/src/main/native/cpp/Solenoid.cpp +++ b/wpilibc/src/main/native/cpp/Solenoid.cpp @@ -111,6 +111,36 @@ bool Solenoid::IsBlackListed() const { return (value != 0); } +/** + * Set the pulse duration in the PCM. This is used in conjunction with + * the startPulse method to allow the PCM to control the timing of a pulse. + * The timing can be controlled in 0.01 second increments. + * + * @param durationSeconds The duration of the pulse, from 0.01 to 2.55 seconds. + * + * @see startPulse() + */ +void Solenoid::SetPulseDuration(double durationSeconds) { + int32_t durationMS = durationSeconds * 1000; + if (StatusIsFatal()) return; + int32_t status = 0; + HAL_SetOneShotDuration(m_solenoidHandle, durationMS, &status); + wpi_setErrorWithContext(status, HAL_GetErrorMessage(status)); +} + +/** + * Trigger the PCM to generate a pulse of the duration set in + * setPulseDuration. + * + * @see setPulseDuration() + */ +void Solenoid::StartPulse() { + if (StatusIsFatal()) return; + int32_t status = 0; + HAL_FireOneShot(m_solenoidHandle, &status); + wpi_setErrorWithContext(status, HAL_GetErrorMessage(status)); +} + void Solenoid::UpdateTable() { if (m_valueEntry) m_valueEntry.SetBoolean(Get()); } diff --git a/wpilibc/src/main/native/include/Solenoid.h b/wpilibc/src/main/native/include/Solenoid.h index 9312c52bed..3f43ced2db 100644 --- a/wpilibc/src/main/native/include/Solenoid.h +++ b/wpilibc/src/main/native/include/Solenoid.h @@ -32,6 +32,8 @@ class Solenoid : public SolenoidBase, public LiveWindowSendable { virtual void Set(bool on); virtual bool Get() const; bool IsBlackListed() const; + void SetPulseDuration(double durationSeconds); + void StartPulse(); void UpdateTable(); void StartLiveWindowMode(); diff --git a/wpilibcIntegrationTests/src/FRCUserProgram/cpp/PCMTest.cpp b/wpilibcIntegrationTests/src/FRCUserProgram/cpp/PCMTest.cpp index bff29050f4..d6d08e371f 100644 --- a/wpilibcIntegrationTests/src/FRCUserProgram/cpp/PCMTest.cpp +++ b/wpilibcIntegrationTests/src/FRCUserProgram/cpp/PCMTest.cpp @@ -157,3 +157,85 @@ TEST_F(PCMTest, DoubleSolenoid) { EXPECT_TRUE(solenoid.Get() == DoubleSolenoid::kReverse) << "Solenoid does not read reverse"; } + +TEST_F(PCMTest, OneShot) { + Reset(); + Solenoid solenoid1(TestBench::kSolenoidChannel1); + Solenoid solenoid2(TestBench::kSolenoidChannel2); + + // Turn both solenoids off + solenoid1.Set(false); + solenoid2.Set(false); + Wait(kSolenoidDelayTime); + EXPECT_TRUE(m_fakeSolenoid1->Get()) << "Solenoid #1 did not turn off"; + EXPECT_TRUE(m_fakeSolenoid2->Get()) << "Solenoid #2 did not turn off"; + EXPECT_FALSE(solenoid1.Get()) << "Solenoid #1 did not read off"; + EXPECT_FALSE(solenoid2.Get()) << "Solenoid #2 did not read off"; + + // Pulse Solenoid #1 on, and turn Solenoid #2 off + solenoid1.SetPulseDuration(2 * kSolenoidDelayTime); + solenoid2.Set(false); + solenoid1.StartPulse(); + Wait(kSolenoidDelayTime); + EXPECT_FALSE(m_fakeSolenoid1->Get()) << "Solenoid #1 did not turn on"; + EXPECT_TRUE(m_fakeSolenoid2->Get()) << "Solenoid #2 did not turn off"; + EXPECT_TRUE(solenoid1.Get()) << "Solenoid #1 did not read on"; + EXPECT_FALSE(solenoid2.Get()) << "Solenoid #2 did not read off"; + Wait(2 * kSolenoidDelayTime); + EXPECT_TRUE(m_fakeSolenoid1->Get()) << "Solenoid #1 did not turn off"; + EXPECT_TRUE(m_fakeSolenoid2->Get()) << "Solenoid #2 did not turn off"; + EXPECT_FALSE(solenoid1.Get()) << "Solenoid #1 did not read off"; + EXPECT_FALSE(solenoid2.Get()) << "Solenoid #2 did not read off"; + + // Turn Solenoid #1 off, and pulse Solenoid #2 on + solenoid1.Set(false); + solenoid2.SetPulseDuration(2 * kSolenoidDelayTime); + solenoid2.StartPulse(); + Wait(kSolenoidDelayTime); + EXPECT_TRUE(m_fakeSolenoid1->Get()) << "Solenoid #1 did not turn off"; + EXPECT_FALSE(m_fakeSolenoid2->Get()) << "Solenoid #2 did not turn on"; + EXPECT_FALSE(solenoid1.Get()) << "Solenoid #1 did not read off"; + EXPECT_TRUE(solenoid2.Get()) << "Solenoid #2 did not read on"; + Wait(2 * kSolenoidDelayTime); + EXPECT_TRUE(m_fakeSolenoid1->Get()) << "Solenoid #1 did not turn off"; + EXPECT_TRUE(m_fakeSolenoid2->Get()) << "Solenoid #2 did not turn off"; + EXPECT_FALSE(solenoid1.Get()) << "Solenoid #1 did not read off"; + EXPECT_FALSE(solenoid2.Get()) << "Solenoid #2 did not read off"; + + // Pulse both Solenoids on + solenoid1.SetPulseDuration(2 * kSolenoidDelayTime); + solenoid2.SetPulseDuration(2 * kSolenoidDelayTime); + solenoid1.StartPulse(); + solenoid2.StartPulse(); + Wait(kSolenoidDelayTime); + EXPECT_FALSE(m_fakeSolenoid1->Get()) << "Solenoid #1 did not turn on"; + EXPECT_FALSE(m_fakeSolenoid2->Get()) << "Solenoid #2 did not turn on"; + EXPECT_TRUE(solenoid1.Get()) << "Solenoid #1 did not read on"; + EXPECT_TRUE(solenoid2.Get()) << "Solenoid #2 did not read on"; + Wait(2 * kSolenoidDelayTime); + EXPECT_TRUE(m_fakeSolenoid1->Get()) << "Solenoid #1 did not turn off"; + EXPECT_TRUE(m_fakeSolenoid2->Get()) << "Solenoid #2 did not turn off"; + EXPECT_FALSE(solenoid1.Get()) << "Solenoid #1 did not read off"; + EXPECT_FALSE(solenoid2.Get()) << "Solenoid #2 did not read off"; + + // Pulse both Solenoids on with different durations + solenoid1.SetPulseDuration(1.5 * kSolenoidDelayTime); + solenoid2.SetPulseDuration(2.5 * kSolenoidDelayTime); + solenoid1.StartPulse(); + solenoid2.StartPulse(); + Wait(kSolenoidDelayTime); + EXPECT_FALSE(m_fakeSolenoid1->Get()) << "Solenoid #1 did not turn on"; + EXPECT_FALSE(m_fakeSolenoid2->Get()) << "Solenoid #2 did not turn on"; + EXPECT_TRUE(solenoid1.Get()) << "Solenoid #1 did not read on"; + EXPECT_TRUE(solenoid2.Get()) << "Solenoid #2 did not read on"; + Wait(kSolenoidDelayTime); + EXPECT_TRUE(m_fakeSolenoid1->Get()) << "Solenoid #1 did not turn off"; + EXPECT_FALSE(m_fakeSolenoid2->Get()) << "Solenoid #2 did not turn on"; + EXPECT_FALSE(solenoid1.Get()) << "Solenoid #1 did not read off"; + EXPECT_TRUE(solenoid2.Get()) << "Solenoid #2 did not read on"; + Wait(2 * kSolenoidDelayTime); + EXPECT_TRUE(m_fakeSolenoid1->Get()) << "Solenoid #1 did not turn off"; + EXPECT_TRUE(m_fakeSolenoid2->Get()) << "Solenoid #2 did not turn off"; + EXPECT_FALSE(solenoid1.Get()) << "Solenoid #1 did not read off"; + EXPECT_FALSE(solenoid2.Get()) << "Solenoid #2 did not read off"; +} diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/Solenoid.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/Solenoid.java index a2a12d58f1..a843a76993 100644 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/Solenoid.java +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/Solenoid.java @@ -98,6 +98,30 @@ public class Solenoid extends SolenoidBase implements LiveWindowSendable { return value != 0; } + /** + * Set the pulse duration in the PCM. This is used in conjunction with + * the startPulse method to allow the PCM to control the timing of a pulse. + * The timing can be controlled in 0.01 second increments. + * + * @param durationSeconds The duration of the pulse, from 0.01 to 2.55 seconds. + * + * @see #startPulse() + */ + public void setPulseDuration(double durationSeconds) { + long durationMS = (long) (durationSeconds * 1000); + SolenoidJNI.setOneShotDuration(m_solenoidHandle, durationMS); + } + + /** + * Trigger the PCM to generate a pulse of the duration set in + * setPulseDuration. + * + * @see #setPulseDuration() + */ + public void startPulse() { + SolenoidJNI.fireOneShot(m_solenoidHandle); + } + /* * Live Window code, only does anything if live window is activated. */ diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/hal/SolenoidJNI.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/hal/SolenoidJNI.java index 965314336c..a1d48c5e88 100644 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/hal/SolenoidJNI.java +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/hal/SolenoidJNI.java @@ -29,4 +29,8 @@ public class SolenoidJNI extends JNIWrapper { public static native boolean getPCMSolenoidVoltageFault(int module); public static native void clearAllPCMStickyFaults(int module); + + public static native void setOneShotDuration(int portHandle, long durationMS); + + public static native void fireOneShot(int portHandle); } diff --git a/wpilibj/src/main/native/cpp/SolenoidJNI.cpp b/wpilibj/src/main/native/cpp/SolenoidJNI.cpp index 0f74ca09c4..63c805e4b3 100644 --- a/wpilibj/src/main/native/cpp/SolenoidJNI.cpp +++ b/wpilibj/src/main/native/cpp/SolenoidJNI.cpp @@ -187,4 +187,40 @@ Java_edu_wpi_first_wpilibj_hal_SolenoidJNI_clearAllPCMStickyFaults( CheckStatus(env, status); } +/* + * Class: edu_wpi_first_wpilibj_hal_SolenoidJNI + * Method: setOneShotDuration + * Signature: (IJ)V + */ +JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_hal_SolenoidJNI_setOneShotDuration + (JNIEnv *env, jclass, jint solenoid_port, jlong durationMS) +{ + SOLENOIDJNI_LOG(logDEBUG) << "Calling SolenoidJNI SetOneShotDuration"; + + SOLENOIDJNI_LOG(logDEBUG) << "Solenoid Port Handle = " + << (HAL_SolenoidHandle)solenoid_port; + SOLENOIDJNI_LOG(logDEBUG) << "Duration (MS) = " << durationMS; + + int32_t status = 0; + HAL_SetOneShotDuration((HAL_SolenoidHandle)solenoid_port, durationMS, &status); + CheckStatus(env, status); +} + +/* + * Class: edu_wpi_first_wpilibj_hal_SolenoidJNI + * Method: fireOneShot + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_hal_SolenoidJNI_fireOneShot + (JNIEnv *env, jclass, jint solenoid_port) +{ + SOLENOIDJNI_LOG(logDEBUG) << "Calling SolenoidJNI fireOneShot"; + + SOLENOIDJNI_LOG(logDEBUG) << "Solenoid Port Handle = " + << (HAL_SolenoidHandle)solenoid_port; + + int32_t status = 0; + HAL_FireOneShot((HAL_SolenoidHandle)solenoid_port, &status); + CheckStatus(env, status); +} } // extern "C" diff --git a/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/PCMTest.java b/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/PCMTest.java index ab0950f64d..62cdc4c8b9 100644 --- a/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/PCMTest.java +++ b/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/PCMTest.java @@ -180,6 +180,95 @@ public class PCMTest extends AbstractComsSetup { solenoid.free(); } + /** + * Test if the correct solenoids turn on and off when they should. + */ + @Test + public void testOneShot() throws Exception { + reset(); + + Solenoid solenoid1 = new Solenoid(0); + Solenoid solenoid2 = new Solenoid(1); + + solenoid1.set(false); + solenoid2.set(false); + Timer.delay(kSolenoidDelayTime); + assertTrue("Solenoid #1 did not turn off", fakeSolenoid1.get()); + assertTrue("Solenoid #2 did not turn off", fakeSolenoid2.get()); + assertFalse("Solenoid #1 did not report off", solenoid1.get()); + assertFalse("Solenoid #2 did not report off", solenoid2.get()); + + // Pulse Solenoid #1 on, and turn Solenoid #2 off + solenoid1.setPulseDuration(2 * kSolenoidDelayTime); + solenoid1.startPulse(); + solenoid2.set(false); + Timer.delay(kSolenoidDelayTime); + assertFalse("Solenoid #1 did not turn on", fakeSolenoid1.get()); + assertTrue("Solenoid #2 did not turn off", fakeSolenoid2.get()); + assertTrue("Solenoid #1 did not report on", solenoid1.get()); + assertFalse("Solenoid #2 did not report off", solenoid2.get()); + Timer.delay(2 * kSolenoidDelayTime); + assertTrue("Solenoid #1 did not turn off", fakeSolenoid1.get()); + assertTrue("Solenoid #2 did not turn off", fakeSolenoid2.get()); + assertFalse("Solenoid #1 did not report off", solenoid1.get()); + assertFalse("Solenoid #2 did not report off", solenoid2.get()); + + // Turn Solenoid #1 off, and pulse Solenoid #2 on + solenoid1.set(false); + solenoid2.setPulseDuration(2 * kSolenoidDelayTime); + solenoid2.startPulse(); + Timer.delay(kSolenoidDelayTime); + assertTrue("Solenoid #1 did not turn off", fakeSolenoid1.get()); + assertFalse("Solenoid #2 did not turn on", fakeSolenoid2.get()); + assertFalse("Solenoid #1 did not report off", solenoid1.get()); + assertTrue("Solenoid #2 did not report on", solenoid2.get()); + Timer.delay(2 * kSolenoidDelayTime); + assertTrue("Solenoid #1 did not turn off", fakeSolenoid1.get()); + assertTrue("Solenoid #2 did not turn off", fakeSolenoid2.get()); + assertFalse("Solenoid #1 did not report off", solenoid1.get()); + assertFalse("Solenoid #2 did not report off", solenoid2.get()); + + // Pulse both Solenoids on + solenoid1.setPulseDuration(2 * kSolenoidDelayTime); + solenoid2.setPulseDuration(2 * kSolenoidDelayTime); + solenoid1.startPulse(); + solenoid2.startPulse(); + Timer.delay(kSolenoidDelayTime); + assertFalse("Solenoid #1 did not turn on", fakeSolenoid1.get()); + assertFalse("Solenoid #2 did not turn on", fakeSolenoid2.get()); + assertTrue("Solenoid #1 did not report on", solenoid1.get()); + assertTrue("Solenoid #2 did not report on", solenoid2.get()); + Timer.delay(2 * kSolenoidDelayTime); + assertTrue("Solenoid #1 did not turn off", fakeSolenoid1.get()); + assertTrue("Solenoid #2 did not turn off", fakeSolenoid2.get()); + assertFalse("Solenoid #1 did not report off", solenoid1.get()); + assertFalse("Solenoid #2 did not report off", solenoid2.get()); + + // Pulse both Solenoids on with different durations + solenoid1.setPulseDuration(1.5 * kSolenoidDelayTime); + solenoid2.setPulseDuration(2.5 * kSolenoidDelayTime); + solenoid1.startPulse(); + solenoid2.startPulse(); + Timer.delay(kSolenoidDelayTime); + assertFalse("Solenoid #1 did not turn on", fakeSolenoid1.get()); + assertFalse("Solenoid #2 did not turn on", fakeSolenoid2.get()); + assertTrue("Solenoid #1 did not report on", solenoid1.get()); + assertTrue("Solenoid #2 did not report on", solenoid2.get()); + Timer.delay(kSolenoidDelayTime); + assertTrue("Solenoid #1 did not turn off", fakeSolenoid1.get()); + assertFalse("Solenoid #2 did not turn on", fakeSolenoid2.get()); + assertFalse("Solenoid #1 did not report off", solenoid1.get()); + assertTrue("Solenoid #2 did not report on", solenoid2.get()); + Timer.delay(kSolenoidDelayTime); + assertTrue("Solenoid #1 did not turn off", fakeSolenoid1.get()); + assertTrue("Solenoid #2 did not turn off", fakeSolenoid2.get()); + assertFalse("Solenoid #1 did not report off", solenoid1.get()); + assertFalse("Solenoid #2 did not report off", solenoid2.get()); + + solenoid1.free(); + solenoid2.free(); + } + protected Logger getClassLogger() { return logger; }