From a501d3ca3bc1eed5d0bc31b4fd5190b0a50fcb7c Mon Sep 17 00:00:00 2001 From: Develo Date: Mon, 26 Nov 2018 10:57:49 -0300 Subject: [PATCH] PolledTimeout Class for wrapping millis() loops (WIP) (#5198) * PolledTimeout Class for wrapping millis() loops * Add yield policies, improve reset, add host tests * Fix copyright, comments * adjust host tests for better time precision * add fuzzyness to timing tests for CI jitter * add blink example with polledTimeout * improve namespace and type naming, add copyright, comments * fix astyle --- cores/esp8266/PolledTimeout.h | 126 +++++++++++++ .../BlinkPolledTimeout/BlinkPolledTimeout.ino | 78 ++++++++ tests/host/Makefile | 3 +- tests/host/core/test_PolledTimeout.cpp | 175 ++++++++++++++++++ 4 files changed, 381 insertions(+), 1 deletion(-) create mode 100644 cores/esp8266/PolledTimeout.h create mode 100644 libraries/esp8266/examples/BlinkPolledTimeout/BlinkPolledTimeout.ino create mode 100644 tests/host/core/test_PolledTimeout.cpp diff --git a/cores/esp8266/PolledTimeout.h b/cores/esp8266/PolledTimeout.h new file mode 100644 index 000000000..7b54577bc --- /dev/null +++ b/cores/esp8266/PolledTimeout.h @@ -0,0 +1,126 @@ +#ifndef __POLLEDTIMING_H__ +#define __POLLEDTIMING_H__ + + +/* + PolledTimeout.h - Encapsulation of a polled Timeout + + Copyright (c) 2018 Daniel Salazar. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + + +namespace esp8266 +{ + + +namespace polledTimeout +{ + +namespace YieldPolicy +{ + +struct DoNothing +{ + static void execute() {} +}; + +struct YieldOrSkip +{ + static void execute() {delay(0);} +}; + +} //YieldPolicy + + +template +class timeoutTemplate +{ +public: + using timeType = decltype(millis()); + + timeoutTemplate(timeType timeout) + : _timeout(timeout), _start(millis()) + {} + + bool expired() + { + YieldPolicyT::execute(); //in case of DoNothing: gets optimized away + if(PeriodicT) //in case of false: gets optimized away + return expiredRetrigger(); + return expiredOneShot(); + } + + operator bool() + { + return expired(); + } + + void reset(timeType newTimeout) + { + _timeout = newTimeout; + reset(); + } + + void reset() + { + _start = millis(); + } + +protected: + bool checkExpired(timeType t) const + { + return (t - _start) >= _timeout; + } + + bool expiredRetrigger() + { + timeType current = millis(); + if(checkExpired(current)) + { + unsigned long n = (current - _start) / _timeout; //how many _timeouts periods have elapsed, will usually be 1 (current - _start >= _timeout) + _start += n * _timeout; + return true; + } + return false; + } + + bool expiredOneShot() const + { + return checkExpired(millis()); + } + + timeType _timeout; + timeType _start; +}; + +using oneShot = polledTimeout::timeoutTemplate; +using periodic = polledTimeout::timeoutTemplate; + +} //polledTimeout + + +/* A 1-shot timeout that auto-yields when in CONT can be built as follows: + * using oneShotYield = esp8266::polledTimeout::timeoutTemplate; + * + * Other policies can be implemented by the user, e.g.: simple yield that panics in SYS, and the polledTimeout types built as needed as shown above, without modifying this file. + */ + +}//esp8266 + +#endif diff --git a/libraries/esp8266/examples/BlinkPolledTimeout/BlinkPolledTimeout.ino b/libraries/esp8266/examples/BlinkPolledTimeout/BlinkPolledTimeout.ino new file mode 100644 index 000000000..12aca929f --- /dev/null +++ b/libraries/esp8266/examples/BlinkPolledTimeout/BlinkPolledTimeout.ino @@ -0,0 +1,78 @@ +/* + ESP8266 Blink with polledTimeout by Daniel Salazar + + Copyright (c) 2018 Daniel Salazar. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + + Note that this sketch uses LED_BUILTIN to find the pin with the internal LED +*/ + + +#include + +void ledOn() { + digitalWrite(LED_BUILTIN, LOW); // Turn the LED on (Note that LOW is the voltage level +} + +void ledOff() { + digitalWrite(LED_BUILTIN, HIGH); // Turn the LED off by making the voltage HIGH +} + +void ledToggle() { + digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // Change the state of the LED +} + + + +esp8266::polledTimeout::periodic halfPeriod(500); //use fully qualified type and avoid importing all ::esp8266 namespace to the global namespace + +// the setup function runs only once at start +void setup() { + pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output + + using esp8266::polledTimeout::oneShot; //import the type to the local namespace + + //STEP1; turn the led ON + ledOn(); + + //STEP2: wait for ON timeout + oneShot timeoutOn(2000); + while (!timeoutOn) { + yield(); + } + + //STEP3: turn the led OFF + ledOff(); + + //STEP4: wait for OFF timeout to assure the led is kept off for this time before exiting setup + oneShot timeoutOff(2000); + while (!timeoutOff) { + yield(); + } + + //Done with STEPs, do other stuff + halfPeriod.reset(); //halfPeriod is global, so it gets inited on sketch start. Clear it here to make it ready for loop, where it's actually used. +} + + +// the loop function runs over and over again forever +void loop() { + if (halfPeriod) { + ledToggle(); + } +} diff --git a/tests/host/Makefile b/tests/host/Makefile index 667fc1533..c83c55fde 100644 --- a/tests/host/Makefile +++ b/tests/host/Makefile @@ -107,7 +107,8 @@ TEST_CPP_FILES := \ fs/test_fs.cpp \ core/test_pgmspace.cpp \ core/test_md5builder.cpp \ - core/test_string.cpp + core/test_string.cpp \ + core/test_PolledTimeout.cpp PREINCLUDES := \ -include common/mock.h \ diff --git a/tests/host/core/test_PolledTimeout.cpp b/tests/host/core/test_PolledTimeout.cpp new file mode 100644 index 000000000..7746072ec --- /dev/null +++ b/tests/host/core/test_PolledTimeout.cpp @@ -0,0 +1,175 @@ +#include +#include "PolledTimeout.h" + +//This won't work for +template +inline bool +fuzzycomp(argT a, argT b) +{ + const argT epsilon = 10; + return (std::max(a,b) - std::min(a,b) <= epsilon); +} + +TEST_CASE("OneShot Timeout 3000ms", "[polledTimeout]") +{ + using esp8266::polledTimeout::oneShot; + using timeType = oneShot::timeType; + timeType before, after, delta; + + Serial.println("OneShot Timeout 3000ms"); + + oneShot timeout(3000); + before = millis(); + while(!timeout.expired()) + yield(); + after = millis(); + + delta = after - before; + Serial.printf("delta = %lu\n", delta); + + REQUIRE(fuzzycomp(delta, (timeType)3000)); + + + Serial.print("reset\n"); + + timeout.reset(); + before = millis(); + while(!timeout) + yield(); + after = millis(); + + delta = after - before; + Serial.printf("delta = %lu\n", delta); + + REQUIRE(fuzzycomp(delta, (timeType)3000)); +} + +TEST_CASE("OneShot Timeout 3000ms reset to 1000ms", "[polledTimeout]") +{ + using esp8266::polledTimeout::oneShot; + using timeType = oneShot::timeType; + timeType before, after, delta; + + Serial.println("OneShot Timeout 3000ms"); + + oneShot timeout(3000); + before = millis(); + while(!timeout.expired()) + yield(); + after = millis(); + + delta = after - before; + Serial.printf("delta = %lu\n", delta); + + REQUIRE(fuzzycomp(delta, (timeType)3000)); + + + Serial.print("reset\n"); + + timeout.reset(1000); + before = millis(); + while(!timeout) + yield(); + after = millis(); + + delta = after - before; + Serial.printf("delta = %lu\n", delta); + + REQUIRE(fuzzycomp(delta, (timeType)1000)); +} + +TEST_CASE("Periodic Timeout 1T 3000ms", "[polledTimeout]") +{ + using esp8266::polledTimeout::periodic; + using timeType = periodic::timeType; + timeType before, after, delta; + + Serial.println("Periodic Timeout 1T 3000ms"); + + periodic timeout(3000); + before = millis(); + while(!timeout) + yield(); + after = millis(); + + delta = after - before; + Serial.printf("delta = %lu\n", delta); + + REQUIRE(fuzzycomp(delta, (timeType)3000)); + + Serial.print("no reset needed\n"); + + before = millis(); + while(!timeout) + yield(); + after = millis(); + + delta = after - before; + Serial.printf("delta = %lu\n", delta); + + REQUIRE(fuzzycomp(delta, (timeType)3000)); +} + +TEST_CASE("Periodic Timeout 10T 1000ms", "[polledTimeout]") +{ + using esp8266::polledTimeout::periodic; + using timeType = periodic::timeType; + timeType before, after, delta; + + Serial.println("Periodic 10T Timeout 1000ms"); + + int counter = 10; + + periodic timeout(1000); + before = millis(); + while(1) + { + if(timeout) + { + Serial.print("*"); + if(!--counter) + break; + yield(); + } + } + after = millis(); + + delta = after - before; + Serial.printf("\ndelta = %lu\n", delta); + REQUIRE(fuzzycomp(delta, (timeType)10000)); +} + +TEST_CASE("OneShot Timeout 3000ms reset to 1000ms custom yield", "[polledTimeout]") +{ + using YieldOrSkipPolicy = esp8266::polledTimeout::YieldPolicy::YieldOrSkip; + using oneShotYield = esp8266::polledTimeout::timeoutTemplate; + using timeType = oneShotYield::timeType; + timeType before, after, delta; + + Serial.println("OneShot Timeout 3000ms"); + + + oneShotYield timeout(3000); + before = millis(); + while(!timeout.expired()); + after = millis(); + + delta = after - before; + Serial.printf("delta = %lu\n", delta); + + REQUIRE(fuzzycomp(delta, (timeType)3000)); + + + Serial.print("reset\n"); + + timeout.reset(1000); + before = millis(); + while(!timeout); + after = millis(); + + delta = after - before; + Serial.printf("delta = %lu\n", delta); + + REQUIRE(fuzzycomp(delta, (timeType)1000)); +} +