Add Debouncer (#3590)

Supersedes #2358 with updates and cleanups.

Closes #2482 and closes #2487 because we shouldn't support both
time-based and count-based debouncing approaches.

Co-authored-by: oblarg <emichaelbarnett@gmail.com>
This commit is contained in:
Tyler Veness
2021-09-19 19:58:16 -07:00
committed by GitHub
parent 179fde3a7b
commit 1ca383b23b
10 changed files with 344 additions and 1 deletions

View File

@@ -6,6 +6,7 @@ package edu.wpi.first.wpilibj2.command.button;
import static edu.wpi.first.wpilibj.util.ErrorMessages.requireNonNullParam;
import edu.wpi.first.wpilibj.Debouncer;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.CommandScheduler;
import edu.wpi.first.wpilibj2.command.InstantCommand;
@@ -359,4 +360,23 @@ public class Trigger {
public Trigger negate() {
return new Trigger(() -> !get());
}
/**
* Creates a new debounced trigger from this trigger - it will become active when this trigger has
* been active for longer than the specified period.
*
* @param seconds the debounce period
* @return the debounced trigger
*/
public Trigger debounce(double seconds) {
return new Trigger(
new BooleanSupplier() {
Debouncer m_debouncer = new Debouncer(seconds);
@Override
public boolean getAsBoolean() {
return m_debouncer.calculate(get());
}
});
}
}

View File

@@ -4,6 +4,8 @@
#include "frc2/command/button/Trigger.h"
#include <frc/Debouncer.h>
#include "frc2/command/InstantCommand.h"
using namespace frc2;
@@ -136,3 +138,9 @@ Trigger Trigger::CancelWhenActive(Command* command) {
});
return *this;
}
Trigger Trigger::Debounce(units::second_t debounceTime) {
return Trigger([debouncer = frc::Debouncer(debounceTime), *this]() mutable {
return debouncer.Calculate(m_isActive());
});
}

View File

@@ -9,6 +9,7 @@
#include <memory>
#include <utility>
#include <units/time.h>
#include <wpi/span.h>
#include "frc2/command/Command.h"
@@ -345,6 +346,15 @@ class Trigger {
return Trigger([*this] { return !m_isActive(); });
}
/**
* Creates a new debounced trigger from this trigger - it will become active
* when this trigger has been active for longer than the specified period.
*
* @param debounceTime the debounce period
* @return the debounced trigger
*/
Trigger Debounce(units::second_t debounceTime);
private:
std::function<bool()> m_isActive;
};

View File

@@ -12,6 +12,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import edu.wpi.first.wpilibj.simulation.SimHooks;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.CommandScheduler;
import edu.wpi.first.wpilibj2.command.CommandTestBase;
@@ -172,4 +173,26 @@ class ButtonTest extends CommandTestBase {
assertFalse(button1.negate().get());
assertTrue(button1.and(button2.negate()).get());
}
@Test
void debounceTest() {
CommandScheduler scheduler = CommandScheduler.getInstance();
MockCommandHolder commandHolder = new MockCommandHolder(true);
Command command = commandHolder.getMock();
InternalButton button = new InternalButton();
Trigger debounced = button.debounce(0.1);
debounced.whenActive(command);
button.setPressed(true);
scheduler.run();
verify(command, never()).schedule(true);
SimHooks.stepTiming(0.3);
button.setPressed(true);
scheduler.run();
verify(command).schedule(true);
}
}

View File

@@ -2,7 +2,9 @@
// 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 "CommandTestBase.h"
#include <frc/simulation/SimHooks.h>
#include "../CommandTestBase.h"
#include "frc2/command/CommandScheduler.h"
#include "frc2/command/RunCommand.h"
#include "frc2/command/WaitUntilCommand.h"
@@ -190,3 +192,19 @@ TEST_F(ButtonTest, RValueButton) {
scheduler.Run();
EXPECT_EQ(counter, 1);
}
TEST_F(ButtonTest, DebounceTest) {
auto& scheduler = CommandScheduler::GetInstance();
bool pressed = false;
RunCommand command([] {});
Trigger([&pressed] { return pressed; }).Debounce(100_ms).WhenActive(&command);
pressed = true;
scheduler.Run();
EXPECT_FALSE(scheduler.IsScheduled(&command));
frc::sim::StepTiming(300_ms);
scheduler.Run();
EXPECT_TRUE(scheduler.IsScheduled(&command));
}