Files
allwpilib/wpilibNewCommands/src/main/native/cpp/frc2/command/button/Trigger.cpp
Michael Lesirge 21b5389bbe [wpimath,cmd] Add multi tap boolean stream filter and multi tap trigger modifier (double tap detector) (#8307)
Add a simple tap counting filter for boolean streams. 

The filter activates when the input has risen (transitioned from false
to true, like when a button is tapped) the required number of times
within the time window after the first rising edge. Once activated, the
output remains true as long as the input is true. The tap count resets
when the time window expires or when the input goes false after
activation.

Example usage:
```java
    xbox.a()
      .multiPress(2, 0.2) // Detect a double tap within 0.2 seconds
      .onTrue(Commands.print("Double tapped A button"));
      
     xbox.y()
      .multiPress(2, 0.5) // Detect a double tap within 0.5 seconds
      .whileTrue(Commands.print("Y held after tap").repeatedly());
```

This is not a noise reduction and/or input smoothing filter, but it is
similar in usage to debounce, so I believe it could be considered a
filter, but am open to a better location.

I believe this would be a useful addition, as double/triple tapping a
button is a common control option in games, yet is not often utilized by
newer FRC teams. I believe adding it to WPILib in a standard way will
allow more teams to make the most out of their controls.
2026-01-14 20:22:07 -08:00

199 lines
5.3 KiB
C++

// 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 "frc2/command/button/Trigger.h"
#include <utility>
#include <frc/filter/Debouncer.h>
#include <frc/filter/EdgeCountFilter.h>
#include "frc2/command/CommandPtr.h"
using namespace frc;
using namespace frc2;
Trigger::Trigger(const Trigger& other) = default;
void Trigger::AddBinding(wpi::unique_function<void(bool, bool)>&& body) {
m_loop->Bind([condition = m_condition, previous = m_condition(),
body = std::move(body)]() mutable {
bool current = condition();
body(previous, current);
previous = current;
});
}
Trigger Trigger::OnChange(Command* command) {
AddBinding([command](bool previous, bool current) {
if (previous != current) {
frc2::CommandScheduler::GetInstance().Schedule(command);
}
});
return *this;
}
Trigger Trigger::OnChange(CommandPtr&& command) {
AddBinding([command = std::move(command)](bool previous, bool current) {
if (previous != current) {
frc2::CommandScheduler::GetInstance().Schedule(command);
}
});
return *this;
}
Trigger Trigger::OnTrue(Command* command) {
AddBinding([command](bool previous, bool current) {
if (!previous && current) {
frc2::CommandScheduler::GetInstance().Schedule(command);
}
});
return *this;
}
Trigger Trigger::OnTrue(CommandPtr&& command) {
AddBinding([command = std::move(command)](bool previous, bool current) {
if (!previous && current) {
frc2::CommandScheduler::GetInstance().Schedule(command);
}
});
return *this;
}
Trigger Trigger::OnFalse(Command* command) {
AddBinding([command](bool previous, bool current) {
if (previous && !current) {
frc2::CommandScheduler::GetInstance().Schedule(command);
}
});
return *this;
}
Trigger Trigger::OnFalse(CommandPtr&& command) {
AddBinding([command = std::move(command)](bool previous, bool current) {
if (previous && !current) {
frc2::CommandScheduler::GetInstance().Schedule(command);
}
});
return *this;
}
Trigger Trigger::WhileTrue(Command* command) {
AddBinding([command](bool previous, bool current) {
if (!previous && current) {
frc2::CommandScheduler::GetInstance().Schedule(command);
} else if (previous && !current) {
command->Cancel();
}
});
return *this;
}
Trigger Trigger::WhileTrue(CommandPtr&& command) {
AddBinding([command = std::move(command)](bool previous, bool current) {
if (!previous && current) {
frc2::CommandScheduler::GetInstance().Schedule(command);
} else if (previous && !current) {
command.Cancel();
}
});
return *this;
}
Trigger Trigger::WhileFalse(Command* command) {
AddBinding([command](bool previous, bool current) {
if (previous && !current) {
frc2::CommandScheduler::GetInstance().Schedule(command);
} else if (!previous && current) {
command->Cancel();
}
});
return *this;
}
Trigger Trigger::WhileFalse(CommandPtr&& command) {
AddBinding([command = std::move(command)](bool previous, bool current) {
if (!previous && current) {
frc2::CommandScheduler::GetInstance().Schedule(command);
} else if (previous && !current) {
command.Cancel();
}
});
return *this;
}
Trigger Trigger::ToggleOnTrue(Command* command) {
AddBinding([command](bool previous, bool current) {
if (!previous && current) {
if (command->IsScheduled()) {
command->Cancel();
} else {
frc2::CommandScheduler::GetInstance().Schedule(command);
}
}
});
return *this;
}
Trigger Trigger::ToggleOnTrue(CommandPtr&& command) {
AddBinding([command = std::move(command)](bool previous, bool current) {
if (!previous && current) {
if (command.IsScheduled()) {
command.Cancel();
} else {
frc2::CommandScheduler::GetInstance().Schedule(command);
}
}
});
return *this;
}
Trigger Trigger::ToggleOnFalse(Command* command) {
AddBinding([command](bool previous, bool current) {
if (previous && !current) {
if (command->IsScheduled()) {
command->Cancel();
} else {
frc2::CommandScheduler::GetInstance().Schedule(command);
}
}
});
return *this;
}
Trigger Trigger::ToggleOnFalse(CommandPtr&& command) {
AddBinding([command = std::move(command)](bool previous, bool current) {
if (previous && !current) {
if (command.IsScheduled()) {
command.Cancel();
} else {
frc2::CommandScheduler::GetInstance().Schedule(command);
}
}
});
return *this;
}
Trigger Trigger::Debounce(units::second_t debounceTime,
frc::Debouncer::DebounceType type) {
return Trigger(m_loop, [debouncer = frc::Debouncer(debounceTime, type),
condition = m_condition]() mutable {
return debouncer.Calculate(condition());
});
}
Trigger Trigger::MultiPress(int requiredPresses, units::second_t windowTime) {
return Trigger(m_loop,
[filter = frc::EdgeCounterFilter(requiredPresses, windowTime),
condition = m_condition]() mutable {
return filter.Calculate(condition());
});
}
bool Trigger::Get() const {
return m_condition();
}