diff --git a/include/etl/debounce_value.h b/include/etl/debounce_value.h new file mode 100644 index 00000000..515a2388 --- /dev/null +++ b/include/etl/debounce_value.h @@ -0,0 +1,258 @@ +///\file + +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 John Wellbelove + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#ifndef ETL_DEBOUNCE_VALUE_INCLUDED +#define ETL_DEBOUNCE_VALUE_INCLUDED + +#include "platform.h" +#include "type_traits.h" + +namespace etl +{ + template + class debounce_value_notification + { + public: + + virtual void on_debounce_value_notification(TValue value) = 0; + }; + + namespace private_debounce_value + { + template + class debounce_value_base + { + public: + + typedef TCount count_type; + typedef TValue value_type; + + typedef debounce_value_notification notification_type; + + //************************************************************************* + /// Constructor. + //************************************************************************* + debounce_value_base(value_type initial_value) + : value(initial_value) + , last_value(initial_value) + , count(0) + , state(Unstable) + , notification(nullptr) + { + } + + //************************************************************************* + /// Adds a new value. + /// If the value has changed, the counter is reset and the value is marked as unstable. + /// Returns 'true' if the value is stable. + //************************************************************************* + bool add(value_type value, count_type stable_count) + { + // Changed from last time? + if (value != last_value) + { + count = 1; + state = Unstable; + } + else + { + // Increment the counter if we haven't reached the stable count yet. + if (count < stable_count) + { + ++count; + + // Check if we've reached the stable count. + if (count == stable_count) + { + state = static_cast(Stable | Changed); + + if (notification) + { + notification->on_debounce_value_notification(value); + } + } + } + else + { + // If the counter has already reached the stable count then we are already stable, but no further change has occurred. + state = Stable; + } + } + + last_value = value; + + return is_stable(); + } + + //************************************************************************* + /// Sets the notification interface. + ///\param notification_ The notification interface. + //************************************************************************* + void set_notification(notification_type& notification_) + { + notification = ¬ification_; + } + + //************************************************************************* + /// Clears the notification interface. + //************************************************************************* + void clear_notification() + { + notification = nullptr; + } + + //************************************************************************* + /// Gets the current value. + //************************************************************************* + value_type get() const + { + return value; + } + + //************************************************************************* + /// Gets the current change state. + ///\return 'true' if the value has changed. + //************************************************************************* + bool has_changed() const + { + return (state & Changed) != 0; + } + + //************************************************************************* + /// Gets the current stable state. + ///\return 'true' if the value is stable. + //************************************************************************* + bool is_stable() const + { + return (state & Stable) != 0; + } + + protected: + + enum State + { + Unstable = 0, + Stable = 1, + Changed = 2 + }; + + value_type value; + value_type last_value; + count_type count; + notification_type* notification; + State state; + }; + } // namespace private_debounce_value + + template + class debounce_value; + + //************************************************************************* + /// A debounced for a value. + /// The debounced will only change its value if the new value has been stable for a given number of calls. + /// Compile-time stable count. + //************************************************************************* + template + class debounce_value> // (2) compile-time specialization + : public private_debounce_value::debounce_value_base + { + public: + + using value_type = typename private_debounce_value::debounce_value_base::value_type; + using count_type = typename private_debounce_value::debounce_value_base::count_type; + + //************************************************************************* + /// Constructor. + //************************************************************************* + explicit debounce_value(value_type initial_value) + : private_debounce_value::debounce_value_base(initial_value) + { + } + + //************************************************************************* + /// Adds a new value to the debounced. + /// If the value has changed, the counter is reset and the debounced is marked as unstable. + //************************************************************************* + bool add(value_type value) + { + return private_debounce_value::debounce_value_base::add(value, Stable_Count); + } + }; + + //************************************************************************* + /// A debounced for a value. + /// The debounced will only change its value if the new value has been stable for a given number of calls. + /// Runtime stable count. + //************************************************************************* + struct debounce_runtime_tag + { + }; + + template + class debounce_value : public private_debounce_value::debounce_value_base + { + public: + + using value_type = typename private_debounce_value::debounce_value_base::value_type; + using count_type = typename private_debounce_value::debounce_value_base::count_type; + + //************************************************************************* + /// Constructor. + //************************************************************************* + explicit debounce_value(value_type initial_value, count_type stable_count) + : private_debounce_value::debounce_value_base(initial_value) + , stable_count(stable_count) + { + } + + //************************************************************************* + /// Adds a new value to the debounced. + /// If the value has changed, the counter is reset and the debounced is marked as unstable. + //************************************************************************* + bool add(value_type value) + { + return private_debounce_value::debounce_value_base::add(value, stable_count); + } + + //************************************************************************* + /// Sets the stable count. + //************************************************************************* + void set_stable_count(count_type stable_count_) + { + stable_count = stable_count_; + } + + private: + + count_type stable_count; + }; +} // namespace etl + +#endif diff --git a/test/test_debounce_value.cpp b/test/test_debounce_value.cpp new file mode 100644 index 00000000..d13a50b0 --- /dev/null +++ b/test/test_debounce_value.cpp @@ -0,0 +1,125 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 John Wellbelove + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#include "unit_test_framework.h" + +#include "etl/debounce_value.h" + +namespace +{ + uint8_t notification_value = 0; + + class Notification : public etl::debounce_value::notification_type + { + void on_debounce_value_notification(uint8_t value) override + { + notification_value = value; + } + }; + + Notification notification; + + SUITE(test_debounce) + { + TEST(test_debounce_value_runtime) + { + const uint8_t Initial_Value = 1; + const uint8_t Stable_Count = 4; + + etl::debounce_value value(Initial_Value, Stable_Count); + + value.set_notification(notification); + + notification_value = 0; + + CHECK_FALSE(value.is_stable()); + CHECK_FALSE(value.has_changed()); + + //******************************* + // Transition from unstable to stable. + CHECK_FALSE(value.add(2)); + CHECK_FALSE(value.is_stable()); + CHECK_FALSE(value.has_changed()); + + CHECK_FALSE(value.add(2)); + CHECK_FALSE(value.is_stable()); + CHECK_FALSE(value.has_changed()); + + CHECK_FALSE(value.add(2)); + CHECK_FALSE(value.is_stable()); + CHECK_FALSE(value.has_changed()); + + // Not notified yet. + CHECK_EQUAL(0, notification_value); + + // Stable & changed. + CHECK_TRUE(value.add(2)); + CHECK_TRUE(value.is_stable()); + CHECK_TRUE(value.has_changed()); + + // Notified. + CHECK_EQUAL(2, notification_value); + + // Still stable. + CHECK_TRUE(value.add(2)); + CHECK_TRUE(value.is_stable()); + CHECK_FALSE(value.has_changed()); + + //******************************* + // Transition from unstable to stable. + // Unstable. + CHECK_FALSE(value.add(3)); + CHECK_FALSE(value.is_stable()); + CHECK_FALSE(value.has_changed()); + + CHECK_FALSE(value.add(3)); + CHECK_FALSE(value.is_stable()); + CHECK_FALSE(value.has_changed()); + + CHECK_FALSE(value.add(3)); + CHECK_FALSE(value.is_stable()); + CHECK_FALSE(value.has_changed()); + + // Not notified yet. + CHECK_EQUAL(2, notification_value); + + // Stable & changed. + CHECK_TRUE(value.add(3)); + CHECK_TRUE(value.is_stable()); + CHECK_TRUE(value.has_changed()); + + // Notified. + CHECK_EQUAL(3, notification_value); + + // Stable. + CHECK_TRUE(value.add(3)); + CHECK_TRUE(value.is_stable()); + CHECK_FALSE(value.has_changed()); + } + } +} // namespace diff --git a/test/vs2022/etl.vcxproj b/test/vs2022/etl.vcxproj index bd6391bb..e0e4fc2d 100644 --- a/test/vs2022/etl.vcxproj +++ b/test/vs2022/etl.vcxproj @@ -3539,6 +3539,7 @@ + @@ -3564,6 +3565,7 @@ + @@ -10390,6 +10392,7 @@ + @@ -10408,6 +10411,7 @@ +