diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/Trigger.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/Trigger.java index 65c6145f6b..fc8b3b1bbe 100644 --- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/Trigger.java +++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/Trigger.java @@ -7,6 +7,7 @@ package edu.wpi.first.wpilibj2.command.button; import static edu.wpi.first.util.ErrorMessages.requireNonNullParam; import edu.wpi.first.math.filter.Debouncer; +import edu.wpi.first.math.filter.EdgeCounterFilter; import edu.wpi.first.wpilibj.event.EventLoop; import edu.wpi.first.wpilibj2.command.Command; import edu.wpi.first.wpilibj2.command.CommandScheduler; @@ -287,4 +288,31 @@ public class Trigger implements BooleanSupplier { } }); } + + /** + * Creates a new multi-press trigger from this trigger - it will become active when this trigger + * has been activated the required number of times within the specified time window. + * + *
This is useful for implementing "double-click" style functionality. + * + *
Input for this must be stable, consider using a Debouncer before this to avoid counting
+ * noise as multiple presses.
+ *
+ * @param requiredPresses The number of presses required.
+ * @param windowTime The number of seconds in which the presses must occur.
+ * @return The multi-press trigger.
+ */
+ public Trigger multiPress(int requiredPresses, double windowTime) {
+ return new Trigger(
+ m_loop,
+ new BooleanSupplier() {
+ final EdgeCounterFilter m_edgeCounterFilter =
+ new EdgeCounterFilter(requiredPresses, windowTime);
+
+ @Override
+ public boolean getAsBoolean() {
+ return m_edgeCounterFilter.calculate(m_condition.getAsBoolean());
+ }
+ });
+ }
}
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/button/Trigger.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/button/Trigger.cpp
index 6afdbb2b2c..6716fe6bc2 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/button/Trigger.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/button/Trigger.cpp
@@ -7,6 +7,7 @@
#include This is useful for implementing "double-click" style functionality.
+ *
+ * Input for this must be stable, consider using a Debouncer before this to
+ * avoid counting noise as multiple presses.
+ *
+ * @param requiredPresses The number of presses required.
+ * @param windowTime The time in which the presses must occur.
+ * @return The multi-press trigger.
+ */
+ Trigger MultiPress(int requiredPresses, units::second_t windowTime);
+
/**
* Returns the current state of this trigger.
*
diff --git a/wpimath/src/main/java/edu/wpi/first/math/filter/EdgeCounterFilter.java b/wpimath/src/main/java/edu/wpi/first/math/filter/EdgeCounterFilter.java
new file mode 100644
index 0000000000..7a4d80e296
--- /dev/null
+++ b/wpimath/src/main/java/edu/wpi/first/math/filter/EdgeCounterFilter.java
@@ -0,0 +1,120 @@
+// 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.math.filter;
+
+import edu.wpi.first.math.MathSharedStore;
+
+/**
+ * A rising edge counter for boolean streams. Requires that the boolean change value to true for a
+ * specified number of times within a specified time window after the first rising edge before the
+ * filtered value changes.
+ *
+ * The filter activates when the input has risen (transitioned from false to true) the required
+ * number of times within the time window. Once activated, the output remains true as long as the
+ * input is true. The edge count resets when the time window expires or when the input goes false
+ * after activation.
+ *
+ * Input must be stable; consider using a Debouncer before this filter to avoid counting noise as
+ * multiple edges.
+ */
+public class EdgeCounterFilter {
+ private int m_requiredEdges;
+ private double m_windowTimeSeconds;
+
+ private double m_firstEdgeTimeSeconds;
+ private int m_currentCount;
+
+ private boolean m_lastInput;
+
+ /**
+ * Creates a new EdgeCounterFilter.
+ *
+ * @param requiredEdges The number of rising edges required before the output goes true.
+ * @param windowTime The maximum number of seconds in which all required edges must occur after
+ * the first rising edge.
+ */
+ public EdgeCounterFilter(int requiredEdges, double windowTime) {
+ m_requiredEdges = requiredEdges;
+ m_windowTimeSeconds = windowTime;
+
+ resetTimer();
+ }
+
+ private void resetTimer() {
+ m_firstEdgeTimeSeconds = MathSharedStore.getTimestamp();
+ }
+
+ private boolean hasElapsed() {
+ return MathSharedStore.getTimestamp() - m_firstEdgeTimeSeconds >= m_windowTimeSeconds;
+ }
+
+ /**
+ * Applies the edge counter filter to the input stream.
+ *
+ * @param input The current value of the input stream.
+ * @return True if the required number of edges have occurred within the time window and the input
+ * is currently true; false otherwise.
+ */
+ public boolean calculate(boolean input) {
+ boolean enoughEdges = m_currentCount >= m_requiredEdges;
+
+ boolean expired = hasElapsed() && !enoughEdges;
+ boolean activationEnded = !input && enoughEdges;
+
+ if (expired || activationEnded) {
+ m_currentCount = 0;
+ }
+
+ if (input && !m_lastInput) {
+ if (m_currentCount == 0) {
+ resetTimer(); // Start timer on first rising edge
+ }
+
+ m_currentCount++;
+ }
+
+ m_lastInput = input;
+
+ return input && m_currentCount >= m_requiredEdges;
+ }
+
+ /**
+ * Sets the time window duration.
+ *
+ * @param windowTime The maximum window of seconds in which all required edges must occur after
+ * the first rising edge.
+ */
+ public void setWindowTime(double windowTime) {
+ m_windowTimeSeconds = windowTime;
+ }
+
+ /**
+ * Gets the time window duration.
+ *
+ * @return The maximum window of seconds in which all required edges must occur after the first
+ * rising edge.
+ */
+ public double getWindowTime() {
+ return m_windowTimeSeconds;
+ }
+
+ /**
+ * Sets the required number of edges.
+ *
+ * @param requiredEdges The number of rising edges required before the output goes true.
+ */
+ public void setRequiredEdges(int requiredEdges) {
+ m_requiredEdges = requiredEdges;
+ }
+
+ /**
+ * Gets the required number of edges.
+ *
+ * @return The number of rising edges required before the output goes true.
+ */
+ public int getRequiredEdges() {
+ return m_requiredEdges;
+ }
+}
diff --git a/wpimath/src/main/native/cpp/filter/EdgeCountFilter.cpp b/wpimath/src/main/native/cpp/filter/EdgeCountFilter.cpp
new file mode 100644
index 0000000000..dfa5e47ff4
--- /dev/null
+++ b/wpimath/src/main/native/cpp/filter/EdgeCountFilter.cpp
@@ -0,0 +1,46 @@
+// 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/filter/EdgeCountFilter.h"
+
+#include "wpimath/MathShared.h"
+
+using namespace frc;
+
+EdgeCounterFilter::EdgeCounterFilter(int requiredEdges, units::second_t window)
+ : m_requiredEdges(requiredEdges), m_windowTime(window) {
+ ResetTimer();
+}
+
+void EdgeCounterFilter::ResetTimer() {
+ m_firstEdgeTime = wpi::math::MathSharedStore::GetTimestamp();
+}
+
+bool EdgeCounterFilter::HasElapsed() const {
+ return wpi::math::MathSharedStore::GetTimestamp() - m_firstEdgeTime >=
+ m_windowTime;
+}
+
+bool EdgeCounterFilter::Calculate(bool input) {
+ bool enoughEdges = m_currentCount >= m_requiredEdges;
+
+ bool expired = HasElapsed() && !enoughEdges;
+ bool activationEnded = !input && enoughEdges;
+
+ if (expired || activationEnded) {
+ m_currentCount = 0;
+ }
+
+ if (input && !m_lastInput) {
+ if (m_currentCount == 0) {
+ ResetTimer(); // Start timer on first rising edge
+ }
+
+ m_currentCount++;
+ }
+
+ m_lastInput = input;
+
+ return input && m_currentCount >= m_requiredEdges;
+}
diff --git a/wpimath/src/main/native/include/frc/filter/EdgeCountFilter.h b/wpimath/src/main/native/include/frc/filter/EdgeCountFilter.h
new file mode 100644
index 0000000000..3fb682d24c
--- /dev/null
+++ b/wpimath/src/main/native/include/frc/filter/EdgeCountFilter.h
@@ -0,0 +1,94 @@
+// 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