From d45d0ee438b418a7ddc40cfeec76986c40c3be8f Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Mon, 10 Aug 2020 10:41:00 -0400 Subject: [PATCH 01/31] First phase of conversion of library to modern C++ --- src/arduino-timer.h | 359 +++++++++++++++++++++++++------------------- 1 file changed, 205 insertions(+), 154 deletions(-) diff --git a/src/arduino-timer.h b/src/arduino-timer.h index 48a56d0..44db6ca 100644 --- a/src/arduino-timer.h +++ b/src/arduino-timer.h @@ -32,171 +32,222 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _CM_ARDUINO_TIMER_H__ -#define _CM_ARDUINO_TIMER_H__ +#ifndef _KPF_ARDUINO_TIMER_H__ +#define _KPF_ARDUINO_TIMER_H__ -#if defined(ARDUINO) && ARDUINO >= 100 #include -#else -#include -#endif + +#undef max +#undef min + +#include +#include +#include +#include +#include +#include #ifndef TIMER_MAX_TASKS #define TIMER_MAX_TASKS 0x10 #endif +#ifndef TIMERSET_DEFAULT_TIMERS + #define TIMERSET_DEFAULT_TIMERS 0x10 +#endif + +namespace Timers { + +typedef unsigned long Timepoint; + +enum class TimerStatus + { + complete, + repeat, + reschedule + }; + +typedef std::tuple HandlerResult; + +typedef std::function Handler; + +struct Timer { + Handler handler; + Timepoint start; // when timer was added (or repeat execution began) + Timepoint expires; // when the timer expires + Timepoint repeat; // default repeat interval + + // ensure that these objects will never be copied or moved + // (this could only happen by accident) + Timer() = default; + Timer(const Timer&) = delete; + Timer& operator=(const Timer&) = delete; +}; + +typedef std::optional> TimerHandle; + template < - size_t max_tasks = TIMER_MAX_TASKS, /* max allocated tasks */ - unsigned long (*time_func)() = millis, /* time function for timer */ - typename T = void * /* handler argument type */ -> -class Timer { - public: - - typedef uintptr_t Task; /* public task handle */ - typedef bool (*handler_t)(T opaque); /* task handler func signature */ - - /* Calls handler with opaque as argument in delay units of time */ - Task - in(unsigned long delay, handler_t h, T opaque = T()) - { - return task_id(add_task(time_func(), delay, h, opaque)); - } - - /* Calls handler with opaque as argument at time */ - Task - at(unsigned long time, handler_t h, T opaque = T()) - { - const unsigned long now = time_func(); - return task_id(add_task(now, time - now, h, opaque)); - } - - /* Calls handler with opaque as argument every interval units of time */ - Task - every(unsigned long interval, handler_t h, T opaque = T()) - { - return task_id(add_task(time_func(), interval, h, opaque, interval)); - } - - /* Cancel the timer task */ - void - cancel(Task &task) - { - if (!task) return; - - for (size_t i = 0; i < max_tasks; ++i) { - struct task * const t = &tasks[i]; - - if (t->handler && (t->id ^ task) == (uintptr_t)t) { - remove(t); - break; - } - } - - task = (Task)NULL; - } - - /* Ticks the timer forward - call this function in loop() */ - unsigned long - tick() - { - unsigned long ticks = (unsigned long)-1; - - for (size_t i = 0; i < max_tasks; ++i) { - struct task * const task = &tasks[i]; - - if (task->handler) { - const unsigned long t = time_func(); - const unsigned long duration = t - task->start; - - if (duration >= task->expires) { - task->repeat = task->handler(task->opaque) && task->repeat; - - if (task->repeat) task->start = t; - else remove(task); - } else { - const unsigned long remaining = task->expires - duration; - ticks = remaining < ticks ? remaining : ticks; - } - } - } - - return ticks == (unsigned long)-1 ? 0 : ticks; - } - - private: - - size_t ctr; - - struct task { - handler_t handler; /* task handler callback func */ - T opaque; /* argument given to the callback handler */ - unsigned long start, - expires; /* when the task expires */ - size_t repeat, /* repeat task */ - id; - } tasks[max_tasks]; - - inline - void - remove(struct task *task) - { - task->handler = NULL; - task->opaque = T(); - task->start = 0; - task->expires = 0; - task->repeat = 0; - task->id = 0; - } - - inline - Task - task_id(const struct task * const t) - { - const Task id = (Task)t; - - return id ? id ^ t->id : id; - } - - inline - struct task * - next_task_slot() - { - for (size_t i = 0; i < max_tasks; ++i) { - struct task * const slot = &tasks[i]; - if (slot->handler == NULL) return slot; - } - - return NULL; - } - - inline - struct task * - add_task(unsigned long start, unsigned long expires, - handler_t h, T opaque, bool repeat = 0) - { - struct task * const slot = next_task_slot(); - - if (!slot) return NULL; - - if (++ctr == 0) ++ctr; // overflow - - slot->id = ctr; - slot->handler = h; - slot->opaque = opaque; - slot->start = start; - slot->expires = expires; - slot->repeat = repeat; - - return slot; - } + size_t max_timers = TIMERSET_DEFAULT_TIMERS, // max number of timers + Timepoint (*time_func)() = millis, // time function for timer + void (*delay_func)(Timepoint) = delay // delay function corresponding to time_func + > +class TimerSet { + std::array timers; + + inline + void + remove(TimerHandle handle) + { + if (!handle) { + return; + } + + auto& timer = handle.value().get(); + + timer.handler = Handler(); + timer.start = 0; + timer.expires = 0; + timer.repeat = 0; + } + + inline + auto + next_timer_slot() + { + return std::find_if(timers.begin(), timers.end(), [](Timer& t){ return !t.handler; }); + } + + inline + TimerHandle + add_timer(Timepoint start, Timepoint expires, Handler h, Timepoint repeat = 0) + { + if (auto timer = next_timer_slot(); timer != timers.end()) { + timer->handler = h; + timer->start = start; + timer->expires = expires; + timer->repeat = repeat; + + return TimerHandle(*timer); + } + else { + return TimerHandle(); + } + } + +public: + // Calls handler in delay units of time + TimerHandle + in(Timepoint delay, Handler h) + { + return add_timer(time_func(), delay, h); + } + + // Calls handler at time + TimerHandle + at(Timepoint time, Handler h) + { + const Timepoint now = time_func(); + return add_timer(now, time - now, h); + } + + // Calls handler every interval units of time + TimerHandle + every(Timepoint interval, Handler h) + { + return add_timer(time_func(), interval, h, interval); + } + + // Calls handler immediately and every interval units of time + TimerHandle + now_and_every(Timepoint interval, Handler h) + { + const Timepoint now = time_func(); + return add_timer(now, now, h, interval); + } + + // Cancels timer + void + cancel(TimerHandle handle) + { + if (!handle) { + return; + } + + auto timer = handle.value().get(); + + if (!timer.handler) { + return; + } + + remove(timer); + } + + // Ticks the timerset forward - call this function in loop() + // returns Timepoint of next timer expiration */ + Timepoint + tick() + { + Timepoint next_expiration = std::numeric_limits::max(); + + // execute handlers for any timers which have expired + for (auto& timer: timers) { + if (!timer.handler) { + continue; + } + + Timepoint now = time_func(); + Timepoint elapsed = now - timer.start; + + if (elapsed >= timer.expires) { + auto [ status, next ] = timer.handler(); + + switch (status) { + case TimerStatus::complete: + remove(timer); + break; + case TimerStatus::repeat: + timer.start = now; + timer.expires = timer.repeat; + break; + case TimerStatus::reschedule: + timer.start = now; + timer.expires = next; + break; + } + } + } + + // compute lowest remaining time after all handlers have been executed + // (some timers may have expired during handler execution) + const Timepoint now = time_func(); + + for (auto& timer: timers) { + if (!timer.handler) { + continue; + } + + Timepoint remaining = timer.expires - (now - timer.start); + next_expiration = remaining < next_expiration ? remaining : next_expiration; + } + + return next_expiration == std::numeric_limits::max() ? 0 : next_expiration; + } + + // Ticks the timerset forward, then delays until next timer is due + void + tick_and_delay() + { + delay_func(tick()); + } }; -/* create a timer with the default settings */ -inline Timer<> -timer_create_default() + +// create TimerSet with default settings +inline TimerSet<> +create_default() { - return Timer<>(); + return TimerSet<>(); } +}; + #endif From 12ea311313e3fa796b65a580813cc3b8b29ce8a4 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Mon, 10 Aug 2020 10:42:39 -0400 Subject: [PATCH 02/31] remove obsolete macro definition --- src/arduino-timer.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/arduino-timer.h b/src/arduino-timer.h index 44db6ca..5a20db4 100644 --- a/src/arduino-timer.h +++ b/src/arduino-timer.h @@ -47,10 +47,6 @@ #include #include -#ifndef TIMER_MAX_TASKS - #define TIMER_MAX_TASKS 0x10 -#endif - #ifndef TIMERSET_DEFAULT_TIMERS #define TIMERSET_DEFAULT_TIMERS 0x10 #endif From c963f0e4730732262c90e648f537bf121f6f96a6 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Mon, 10 Aug 2020 12:09:46 -0400 Subject: [PATCH 03/31] use modern include guard --- src/arduino-timer.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/arduino-timer.h b/src/arduino-timer.h index 5a20db4..1ce4352 100644 --- a/src/arduino-timer.h +++ b/src/arduino-timer.h @@ -32,8 +32,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _KPF_ARDUINO_TIMER_H__ -#define _KPF_ARDUINO_TIMER_H__ +#pragma once #include @@ -245,5 +244,3 @@ create_default() } }; - -#endif From 20c0b6128a7abfb8d53e23b5afc88e51ffcec2ee Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Mon, 10 Aug 2020 12:11:01 -0400 Subject: [PATCH 04/31] use type aliases instead of typedefs --- src/arduino-timer.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/arduino-timer.h b/src/arduino-timer.h index 1ce4352..e02bd24 100644 --- a/src/arduino-timer.h +++ b/src/arduino-timer.h @@ -52,7 +52,7 @@ namespace Timers { -typedef unsigned long Timepoint; +using Timepoint = unsigned long; enum class TimerStatus { @@ -61,9 +61,9 @@ enum class TimerStatus reschedule }; -typedef std::tuple HandlerResult; +using HandlerResult = std::tuple; -typedef std::function Handler; +using Handler = std::function; struct Timer { Handler handler; @@ -78,7 +78,7 @@ struct Timer { Timer& operator=(const Timer&) = delete; }; -typedef std::optional> TimerHandle; +using TimerHandle = std::optional>; template < size_t max_timers = TIMERSET_DEFAULT_TIMERS, // max number of timers From ebf7197d5cba517fa1658d42d3636e59257a034c Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Mon, 10 Aug 2020 12:11:20 -0400 Subject: [PATCH 05/31] use a proper name for the iterator variable --- src/arduino-timer.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/arduino-timer.h b/src/arduino-timer.h index e02bd24..2b863a4 100644 --- a/src/arduino-timer.h +++ b/src/arduino-timer.h @@ -115,13 +115,13 @@ class TimerSet { TimerHandle add_timer(Timepoint start, Timepoint expires, Handler h, Timepoint repeat = 0) { - if (auto timer = next_timer_slot(); timer != timers.end()) { - timer->handler = h; - timer->start = start; - timer->expires = expires; - timer->repeat = repeat; + if (auto it = next_timer_slot(); it != timers.end()) { + it->handler = h; + it->start = start; + it->expires = expires; + it->repeat = repeat; - return TimerHandle(*timer); + return TimerHandle(*it); } else { return TimerHandle(); From 9418ae9079e49dd9e37e0fbc5c673b04b2d5ce56 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Mon, 10 Aug 2020 17:54:51 -0400 Subject: [PATCH 06/31] start updating docs --- README.md | 108 ++++++++++++++++++++++++++++++--------------- keywords.txt | 23 ++++++---- library.properties | 14 +++--- src/timer.h | 3 -- 4 files changed, 93 insertions(+), 55 deletions(-) delete mode 100644 src/timer.h diff --git a/README.md b/README.md index 587e113..813b8e8 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,100 @@ -# arduino-timer - library for delaying function calls +# arduino-timer-cpp17 - library for scheduling function calls -Simple *non-blocking* timer library for calling functions **in / at / every** specified units of time. Supports millis, micros, time rollover, and compile time configurable number of tasks. +Simple *non-blocking* timer library for calling functions **in / at / every** specified units of time. Supports millis, micros, time rollover, and compile-time configurable number of timers. + +This library was inspired by [Michael Contreras' arduino-timer library](https://github.com/sponsors/contrem/arduino-timer), but has been rewritten to make use of 'modern C++' types and +functionality. As a result this library requires that the Arduino IDE toolchain be manually configured for "gnu++17" mode. ### Use It -Include the library and create a *Timer* instance. +Include the library and create a *TimerSet* instance. ```cpp #include -auto timer = timer_create_default(); +auto timerset = Timers::create_default(); ``` -Or using the *Timer* constructors for different task limits / time resolution +Or using the *TimerSet* constructors for different timer limits / time resolution. ```cpp -Timer<10> timer; // 10 concurrent tasks, using millis as resolution -Timer<10, micros> timer; // 10 concurrent tasks, using micros as resolution -Timer<10, micros, int> timer; // 10 concurrent tasks, using micros as resolution, with handler argument of type int +Timers::TimerSet<10> timerset; // 10 concurrent times, using millis as resolution +Timers::TimerSet<10, micros, delayMicroseconds> timerset; // 10 concurrent timers, using micros as resolution ``` -Call *timer*.**tick()** in the loop function +Call *timerset*.**tick_and_delay()** in the ```loop``` function to execute handlers for any timers +which have expired and then delay until the next scheduled timer expiration. ```cpp void loop() { - timer.tick(); + timerset.tick_and_delay(); } ``` -Make a function to call when the *Timer* expires +Call *timerset*.**tick()** in the ```loop``` function to execute handlers for any timers +which have expired, and then return so additional processing can be handled in the loop function. ```cpp -bool function_to_call(void *argument /* optional argument given to in/at/every */) { - return true; // to repeat the action - false to stop +void loop() { + timerset.tick; +} +``` + +Make a function to call (without arguments) when a *Timer* expires. +```cpp +Timers::HandlerResult function_to_call() { + return { Timers::TimerStatus::repeat, 0 }; // to repeat the action - 'completed' to stop +} +``` + +Make a function to call (with an argument) when a *Timer* expires. +```cpp +Timers::HandlerResult function_to_call_with_arg(int value) { + return { Timers::TimerStatus::completed, 0 }; // to stop the timer - 'repeat' to repeat the action +} +``` + +Make a function to call (without arguments) when a *Timer* expires, which can reschedule itself based +on a digital input. In this example, if digital input 4 is active when the function is called, it will +change its own repeat interval to 5 seconds (5000 millis). +```cpp +Timers::HandlerResult function_to_call_and_reschedule() { + if (digitalRead(4)) { + return { Timers::TimerStatus::reschedule, 5000 }; // to change the repeat interval to 5000 ms + } else { + return { Timers::TimerStatus::repeat, 0 }; // to repeat the action - 'completed' to stop + } } ``` Call *function\_to\_call* **in** *delay* units of time *(unit of time defaults to milliseconds)*. ```cpp -timer.in(delay, function_to_call); -timer.in(delay, function_to_call, argument); // or with an optional argument for function_to_call +timerset.in(delay, function_to_call); +``` + +Call *function\_to\_call|_with|_arg* **in** *delay* units of time *(unit of time defaults to milliseconds)*. +```cpp +timerset.in(delay, [](){ return function_to_call_with_arg(42); }); ``` -Call *function\_to\_call* **at** a specific *time*. +Call functions **at** a specific *time*. ```cpp -timer.at(time, function_to_call); -timer.at(time, function_to_call, argument); // with argument +timerset.at(time, function_to_call); +timerset.at(time, [](){ return function_to_call_with_arg(42); }); ``` -Call *function\_to\_call* **every** *interval* units of time. +Call functions **every** *interval* units of time. ```cpp -timer.every(interval, function_to_call); -timer.every(interval, function_to_call, argument); // with argument +timerset.every(interval, function_to_call); +timerset.every(interval, [](){ return function_to_call_with_arg(42); }); ``` -To **cancel** a *Task* +Call functions **now** and **every** *interval* units of time. ```cpp -auto task = timer.in(delay, function_to_call); -timer.cancel(task); +timerset.now_and_every(interval, function_to_call); +timerset.now_and_every(interval, [](){ return function_to_call_with_arg(42); }); ``` -Be fancy with **lambdas** +To **cancel** a *Timer* ```cpp -timer.in(1000, [](void*) -> bool { return false; }); -timer.in(1000, [](void *argument) -> bool { return argument; }, argument); +auto timer = timerset.in(delay, function_to_call); +timerset.cancel(timer); ``` ### API @@ -116,22 +151,22 @@ The simplest example, blinking an LED every second *(from examples/blink)*: ```cpp #include -auto timer = timer_create_default(); // create a timer with default settings +auto timerset = Timers::create_default(); // create a timerset with default settings -bool toggle_led(void *) { +Timers::HandlerResult toggle_led() { digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED - return true; // keep timer active? true + return { Timers::TimerStatus::repeat, 0 }; // keep timer active? true } void setup() { pinMode(LED_BUILTIN, OUTPUT); // set LED pin to OUTPUT // call the toggle_led function every 1000 millis (1 second) - timer.every(1000, toggle_led); + timerset.every(1000, toggle_led); } void loop() { - timer.tick(); // tick the timer + timerset.tick_and_delay(); // tick the timer } ``` @@ -141,14 +176,15 @@ Check the LICENSE file - 3-Clause BSD License ### Notes -Currently only a software timer. Any blocking code delaying *timer*.**tick()** will prevent the timer from moving forward and calling any functions. +Currently only a software timer. Any blocking code delaying *timerset*.**tick()** will prevent the TimerSet from moving forward and calling any functions. The library does not do any dynamic memory allocation. -The number of concurrent tasks is a compile time constant, meaning there is a limit to the number of concurrent tasks. The **in / at / every** functions return **NULL** if the *Timer* is full. +The number of concurrent timers is a compile time constant, meaning there is a limit to the number of concurrent timers. The **in / at / every / now_and_every** +functions return a TimerHandle which evaluates to ```false``` if the TimerSet is full. -A *Task* value is valid only for the timer that created it, and only for the lifetime of that timer. +A *TimerHandle* value is valid only for the TimerSet that created it, and only for the lifetime of that timer. -Change the number of concurrent tasks using the *Timer* constructors. Save memory by reducing the number, increase memory use by having more. The default is **TIMER_MAX_TASKS** which is currently 16. +Change the number of concurrent timers using the *Timer* constructors. Save memory by reducing the number, increase memory use by having more. The default is **TIMERSET_DEFAULT_TIMERS** which is currently 16. If you find this project useful, [consider becoming a sponsor.](https://github.com/sponsors/contrem) diff --git a/keywords.txt b/keywords.txt index 5f9e15b..901f903 100644 --- a/keywords.txt +++ b/keywords.txt @@ -6,19 +6,24 @@ # Datatypes (KEYWORD1) ####################################### -Timer KEYWORD1 -Task KEYWORD1 -handler_t KEYWORD1 +HandlerResult KEYWORD1 +Timepoint KEYWORD1 +Timer KEYWORD1 +TimerHandle KEYWORD1 +TimerSet KEYWORD1 +TimerStatus KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) ####################################### -in KEYWORD2 -at KEYWORD2 -every KEYWORD2 -tick KEYWORD2 -cancel KEYWORD2 +in KEYWORD2 +at KEYWORD2 +every KEYWORD2 +now_and_every KEYWORD2 +tick KEYWORD2 +cancel KEYWORD2 +tick_and_delay KEYWORD2 ####################################### # Instances (KEYWORD2) @@ -28,4 +33,4 @@ cancel KEYWORD2 # Constants (LITERAL1) ####################################### -TIMER_MAX_TASKS LITERAL1 +TIMERSET_DEFAULT_TIMERS LITERAL1 diff --git a/library.properties b/library.properties index 5f4f140..7695ce7 100644 --- a/library.properties +++ b/library.properties @@ -1,11 +1,11 @@ -name=arduino-timer -version=2.1.0 +name=arduino-timer-cpp17 +version=3.0.0 -author=Michael Contreras -maintainer=Michael Contreras -sentence=Timer library for delaying function calls -paragraph=Simple non-blocking timer library for calling functions in / at / every specified units of time. Supports millis, micros, time rollover, and compile time configurable number of tasks. +author=Kevin P. Fleming +maintainer=Kevin P. Fleming +sentence=Timer library for scheduling function calls +paragraph=Simple non-blocking timer library for calling functions in / at / every specified units of time. Supports millis, micros, time rollover, and compile-time configurable number of timers. category=Timing -url=https://github.com/contrem/arduino-timer +url=https://github.com/kpfleming/arduino-timer-cpp17 architectures=* includes=arduino-timer.h diff --git a/src/timer.h b/src/timer.h deleted file mode 100644 index dbe114c..0000000 --- a/src/timer.h +++ /dev/null @@ -1,3 +0,0 @@ -#warning "Including this file is deprecated. Please #include instead." - -#include From 421c4376748310a6867a44e86dc74caf5f90945b Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Mon, 10 Aug 2020 18:11:17 -0400 Subject: [PATCH 07/31] correct formatting errors and other nits --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 813b8e8..b707a5b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Simple *non-blocking* timer library for calling functions **in / at / every** specified units of time. Supports millis, micros, time rollover, and compile-time configurable number of timers. -This library was inspired by [Michael Contreras' arduino-timer library](https://github.com/sponsors/contrem/arduino-timer), but has been rewritten to make use of 'modern C++' types and +This library was inspired by [Michael Contreras' arduino-timer library](https://github.com/contrem/arduino-timer), but has been rewritten to make use of 'modern C++' types and functionality. As a result this library requires that the Arduino IDE toolchain be manually configured for "gnu++17" mode. ### Use It @@ -16,8 +16,8 @@ auto timerset = Timers::create_default(); Or using the *TimerSet* constructors for different timer limits / time resolution. ```cpp -Timers::TimerSet<10> timerset; // 10 concurrent times, using millis as resolution -Timers::TimerSet<10, micros, delayMicroseconds> timerset; // 10 concurrent timers, using micros as resolution +Timers::TimerSet<10> timerset; // 10 concurrent timers, using millis as resolution +Timers::TimerSet<10, micros, delayMicroseconds> microtimerset; // 10 concurrent timers, using micros as resolution ``` Call *timerset*.**tick_and_delay()** in the ```loop``` function to execute handlers for any timers @@ -32,7 +32,7 @@ Call *timerset*.**tick()** in the ```loop``` function to execute handlers for an which have expired, and then return so additional processing can be handled in the loop function. ```cpp void loop() { - timerset.tick; + timerset.tick(); } ``` @@ -144,7 +144,7 @@ void cancel(Timer<>::Task &task); ### Examples -Found in the **examples/** folder. +Found in the [**examples**](examples) folder. The simplest example, blinking an LED every second *(from examples/blink)*: From 1377ab90dee230247892260aaf16c6261baad565 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Mon, 10 Aug 2020 18:11:31 -0400 Subject: [PATCH 08/31] simplify HandlerResult --- README.md | 12 ++++++------ src/arduino-timer.h | 12 ++++++++++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b707a5b..60dfc0d 100644 --- a/README.md +++ b/README.md @@ -39,14 +39,14 @@ void loop() { Make a function to call (without arguments) when a *Timer* expires. ```cpp Timers::HandlerResult function_to_call() { - return { Timers::TimerStatus::repeat, 0 }; // to repeat the action - 'completed' to stop + return { Timers::TimerStatus::repeat }; // to repeat the action - 'completed' to stop } ``` Make a function to call (with an argument) when a *Timer* expires. ```cpp Timers::HandlerResult function_to_call_with_arg(int value) { - return { Timers::TimerStatus::completed, 0 }; // to stop the timer - 'repeat' to repeat the action + return { Timers::TimerStatus::completed }; // to stop the timer - 'repeat' to repeat the action } ``` @@ -57,9 +57,9 @@ change its own repeat interval to 5 seconds (5000 millis). Timers::HandlerResult function_to_call_and_reschedule() { if (digitalRead(4)) { return { Timers::TimerStatus::reschedule, 5000 }; // to change the repeat interval to 5000 ms - } else { - return { Timers::TimerStatus::repeat, 0 }; // to repeat the action - 'completed' to stop - } + } else { + return { Timers::TimerStatus::repeat }; // to repeat the action - 'completed' to stop + } } ``` @@ -155,7 +155,7 @@ auto timerset = Timers::create_default(); // create a timerset with default sett Timers::HandlerResult toggle_led() { digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED - return { Timers::TimerStatus::repeat, 0 }; // keep timer active? true + return { Timers::TimerStatus::repeat }; // keep timer active? true } void setup() { diff --git a/src/arduino-timer.h b/src/arduino-timer.h index 2b863a4..1d43343 100644 --- a/src/arduino-timer.h +++ b/src/arduino-timer.h @@ -44,7 +44,6 @@ #include #include #include -#include #ifndef TIMERSET_DEFAULT_TIMERS #define TIMERSET_DEFAULT_TIMERS 0x10 @@ -61,7 +60,16 @@ enum class TimerStatus reschedule }; -using HandlerResult = std::tuple; +struct HandlerResult { + TimerStatus status; + Timepoint next; + + // must be constructed with at least a status, but can be constructed + // with status and next + HandlerResult() = delete; + HandlerResult(TimerStatus status) : status(status) {} + HandlerResult(TimerStatus status, Timepoint next) : status(status), next(next) {} +}; using Handler = std::function; From ab5a84412335035b5ef828c0cde80b0489412fc1 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Mon, 10 Aug 2020 18:22:25 -0400 Subject: [PATCH 09/31] rename header file --- README.md | 2 +- library.properties | 2 +- src/{arduino-timer.h => arduino-timer-cpp17.h} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/{arduino-timer.h => arduino-timer-cpp17.h} (100%) diff --git a/README.md b/README.md index 60dfc0d..bde0060 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ functionality. As a result this library requires that the Arduino IDE toolchain Include the library and create a *TimerSet* instance. ```cpp -#include +#include auto timerset = Timers::create_default(); ``` diff --git a/library.properties b/library.properties index 7695ce7..6516639 100644 --- a/library.properties +++ b/library.properties @@ -8,4 +8,4 @@ paragraph=Simple non-blocking timer library for calling functions in / at / ever category=Timing url=https://github.com/kpfleming/arduino-timer-cpp17 architectures=* -includes=arduino-timer.h +includes=arduino-timer-cpp17.h diff --git a/src/arduino-timer.h b/src/arduino-timer-cpp17.h similarity index 100% rename from src/arduino-timer.h rename to src/arduino-timer-cpp17.h From 5a91bdda4a6fb3928c104a4ff5bcb312c6c160ab Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Mon, 10 Aug 2020 18:47:35 -0400 Subject: [PATCH 10/31] correct library name in Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 4430b14..57ab510 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -NAME := arduino-timer +NAME := arduino-timer-cpp17 VERSION := $(shell git describe --tags --always --dirty) $(NAME)-$(VERSION).zip: From 381b7ef2c5403cd38a511ae81263b28e864843eb Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Mon, 10 Aug 2020 18:48:12 -0400 Subject: [PATCH 11/31] even simpler HandlerResult syntax --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bde0060..d212244 100644 --- a/README.md +++ b/README.md @@ -39,14 +39,14 @@ void loop() { Make a function to call (without arguments) when a *Timer* expires. ```cpp Timers::HandlerResult function_to_call() { - return { Timers::TimerStatus::repeat }; // to repeat the action - 'completed' to stop + return Timers::TimerStatus::repeat; // to repeat the action - 'completed' to stop } ``` Make a function to call (with an argument) when a *Timer* expires. ```cpp Timers::HandlerResult function_to_call_with_arg(int value) { - return { Timers::TimerStatus::completed }; // to stop the timer - 'repeat' to repeat the action + return Timers::TimerStatus::completed; // to stop the timer - 'repeat' to repeat the action } ``` @@ -58,7 +58,7 @@ Timers::HandlerResult function_to_call_and_reschedule() { if (digitalRead(4)) { return { Timers::TimerStatus::reschedule, 5000 }; // to change the repeat interval to 5000 ms } else { - return { Timers::TimerStatus::repeat }; // to repeat the action - 'completed' to stop + return Timers::TimerStatus::repeat; // to repeat the action - 'completed' to stop } } ``` @@ -155,7 +155,7 @@ auto timerset = Timers::create_default(); // create a timerset with default sett Timers::HandlerResult toggle_led() { digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED - return { Timers::TimerStatus::repeat }; // keep timer active? true + return Timers::TimerStatus::repeat; // keep timer active? true } void setup() { From d560fb3956ea4dc04a9fdb1ad6468cb01938fac2 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Mon, 10 Aug 2020 18:48:37 -0400 Subject: [PATCH 12/31] initial update of examples --- examples/blink/blink.ino | 14 +++--- examples/blink_micros/blink_micros.ino | 14 +++--- examples/blink_print/blink_print.ino | 20 ++++---- examples/full/full.ino | 64 ++++++++++++-------------- 4 files changed, 54 insertions(+), 58 deletions(-) diff --git a/examples/blink/blink.ino b/examples/blink/blink.ino index 6b0b725..71d6c0d 100644 --- a/examples/blink/blink.ino +++ b/examples/blink/blink.ino @@ -1,26 +1,26 @@ /* * timer_blink * - * Blinks the built-in LED every second using the arduino-timer library. + * Blinks the built-in LED every second using the arduino-timer-cpp17 library. * */ -#include +#include -auto timer = timer_create_default(); // create a timer with default settings +auto timerset = Timers::create_default(); // create a TimerSet with default settings -bool toggle_led(void *) { +Timers::HandlerResult toggle_led() { digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED - return true; // repeat? true + return Timers::TimerStatus::repeat; } void setup() { pinMode(LED_BUILTIN, OUTPUT); // set LED pin to OUTPUT // call the toggle_led function every 1000 millis (1 second) - timer.every(1000, toggle_led); + timerset.every(1000, toggle_led); } void loop() { - timer.tick(); // tick the timer + timerset.tick_and_delay(); // tick the timer } diff --git a/examples/blink_micros/blink_micros.ino b/examples/blink_micros/blink_micros.ino index 0ee415b..3ddb9b7 100644 --- a/examples/blink_micros/blink_micros.ino +++ b/examples/blink_micros/blink_micros.ino @@ -1,26 +1,26 @@ /* * timer_blink_micros * - * Blinks the built-in LED every second using the arduino-timer library. + * Blinks the built-in LED every second using the arduino-timer-cpp17 library. * */ -#include +#include -Timer<1, micros> timer; // create a timer with 1 task and microsecond resolution +Timers::TimerSet<1, micros, delayMicroseconds> timerset; // create a TimerSet with 1 timer and microsecond resolution -bool toggle_led(void *) { +Timers::HandlerResult toggle_led() { digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED - return true; // repeat? true + return Timers::TimerStatus::repeat; } void setup() { pinMode(LED_BUILTIN, OUTPUT); // set LED pin to OUTPUT // call the toggle_led function every 1000000 micros (1 second) - timer.every(1000000, toggle_led); + timerset.every(1000000, toggle_led); } void loop() { - timer.tick(); // tick the timer + timerset.tick_and_delay(); // tick the timer } diff --git a/examples/blink_print/blink_print.ino b/examples/blink_print/blink_print.ino index 3a73636..bc2098b 100644 --- a/examples/blink_print/blink_print.ino +++ b/examples/blink_print/blink_print.ino @@ -2,23 +2,23 @@ * timer_blink_print * * Blinks the built-in LED every half second, and prints a messages every - * second using the arduino-timer library. + * second using the arduino-timer-cpp17 library. * */ -#include +#include -auto timer = timer_create_default(); // create a timer with default settings +auto timerset = Timers::create_default(); // create a TimerSet with default settings -bool toggle_led(void *) { +Timers::HandlerResult toggle_led() { digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED - return true; // repeat? true + return Timers::TimerStatus::repeat; } -bool print_message(void *) { +Timers::HandlerResult print_message() { Serial.print("print_message: Called at: "); Serial.println(millis()); - return true; // repeat? true + return Timers::TimerStatus::repeat; } void setup() { @@ -26,12 +26,12 @@ void setup() { pinMode(LED_BUILTIN, OUTPUT); // set LED pin to OUTPUT // call the toggle_led function every 500 millis (half second) - timer.every(500, toggle_led); + timerset.every(500, toggle_led); // call the print_message function every 1000 millis (1 second) - timer.every(1000, print_message); + timerset.every(1000, print_message); } void loop() { - timer.tick(); // tick the timer + timerset.tick_and_delay(); // tick the timer } diff --git a/examples/full/full.ino b/examples/full/full.ino index 590cd3b..25f2558 100644 --- a/examples/full/full.ino +++ b/examples/full/full.ino @@ -1,7 +1,7 @@ /* * timer_full * - * Full example using the arduino-timer library. + * Full example using the arduino-timer-cpp17 library. * Shows: * - Setting a different number of tasks with microsecond resolution * - disabling a repeated function @@ -10,41 +10,37 @@ * */ -#include +#include -auto timer = timer_create_default(); // create a timer with default settings -Timer<> default_timer; // save as above +auto timerset = Timers::create_default(); // create a TimerSet with default settings +Timers::TimerSet<> default_timerset; // same as above -// create a timer that can hold 1 concurrent task, with microsecond resolution -// and a custom handler type of 'const char * -Timer<1, micros, const char *> u_timer; +// create a TimerSet that can hold 1 concurrent task, with microsecond resolution +Timers::TimerSet<1, micros, delayMicroseconds> microtimerset; +// create a TimerSet that holds 16 tasks, with millisecond resolution +Timers::TimerSet<16, millis> t_timerset; -// create a timer that holds 16 tasks, with millisecond resolution, -// and a custom handler type of 'const char * -Timer<16, millis, const char *> t_timer; - -bool toggle_led(void *) { +Timers::HandlerResult toggle_led() { digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED - return true; // repeat? true + return Timers::TimerStatus::repeat; } -bool print_message(const char *m) { +Timers::HandlerResult print_message(const char *message) { Serial.print("print_message: "); - Serial.println(m); - return true; // repeat? true + Serial.println(message); + return Timers::TimerStatus::repeat; } size_t repeat_count = 1; -bool repeat_x_times(void *opaque) { - size_t limit = (size_t)opaque; - +Timers::HandlerResult repeat_x_times(size_t limit) { Serial.print("repeat_x_times: "); Serial.print(repeat_count); Serial.print("/"); Serial.println(limit); - return ++repeat_count <= limit; // remove this task after limit reached + // remove this task after limit reached + return ++repeat_count <= limit ? Timers::TimerStatus::repeat : Timers::TimerStatus::completed; } void setup() { @@ -52,36 +48,36 @@ void setup() { pinMode(LED_BUILTIN, OUTPUT); // set LED pin to OUTPUT // call the toggle_led function every 500 millis (half second) - timer.every(500, toggle_led); + timerset.every(500, toggle_led); // call the repeat_x_times function every 1000 millis (1 second) - timer.every(1000, repeat_x_times, (void *)10); + timerset.every(1000, [](){ return repeat_x_times(10); }); // call the print_message function every 1000 millis (1 second), // passing it an argument string - t_timer.every(1000, print_message, "called every second"); + t_timerset.every(1000, [](){ return print_message("called every second"); }); // call the print_message function in five seconds - t_timer.in(5000, print_message, "delayed five seconds"); + t_timerset.in(5000, [](){ return print_message("delayed five seconds"); }); // call the print_message function at time + 10 seconds - t_timer.at(millis() + 10000, print_message, "call at millis() + 10 seconds"); + t_timerset.at(millis() + 10000, [](){ return print_message("call at millis() + 10 seconds"); }); - // call the toggle_led function every 500 millis (half second) - auto task = timer.every(500, toggle_led); - timer.cancel(task); // this task is now cancelled, and will not run + // call the toggle_led function every 500 millis (half-second) + auto timer = timerset.every(500, toggle_led); + timerset.cancel(timer); // this task is now cancelled, and will not run // call print_message in 2 seconds, but with microsecond resolution - u_timer.in(2000000, print_message, "delayed two seconds using microseconds"); + microtimerset.in(2000000, [](){ return print_message("delayed two seconds using microseconds"); }); - if (!u_timer.in(5000, print_message, "never printed")) { - /* this fails because we created u_timer with only 1 concurrent task slot */ + if (!microtimerset.in(5000, [](){ return print_message("never printed"); })) { + /* this fails because we created microtimerset with only 1 concurrent timer slot */ Serial.println("Failed to add microsecond event - timer full"); } } void loop() { - timer.tick(); // tick the timer - t_timer.tick(); - u_timer.tick(); + timerset.tick(); + t_timerset.tick(); + microtimerset.tick(); } From 46cc6fe2fd466ba00ff00c66c55d01e7874dace2 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Mon, 10 Aug 2020 20:56:25 -0400 Subject: [PATCH 13/31] use tag dispatch for timer resolution --- README.md | 2 +- examples/blink_micros/blink_micros.ino | 2 +- examples/full/full.ino | 4 +- src/arduino-timer-cpp17.h | 59 ++++++++++++++++++++------ 4 files changed, 50 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index d212244..32f0203 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ auto timerset = Timers::create_default(); Or using the *TimerSet* constructors for different timer limits / time resolution. ```cpp Timers::TimerSet<10> timerset; // 10 concurrent timers, using millis as resolution -Timers::TimerSet<10, micros, delayMicroseconds> microtimerset; // 10 concurrent timers, using micros as resolution +Timers::TimerSet<10, Timers::Resolution::micros> microtimerset; // 10 concurrent timers, using micros as resolution ``` Call *timerset*.**tick_and_delay()** in the ```loop``` function to execute handlers for any timers diff --git a/examples/blink_micros/blink_micros.ino b/examples/blink_micros/blink_micros.ino index 3ddb9b7..707a4fd 100644 --- a/examples/blink_micros/blink_micros.ino +++ b/examples/blink_micros/blink_micros.ino @@ -7,7 +7,7 @@ #include -Timers::TimerSet<1, micros, delayMicroseconds> timerset; // create a TimerSet with 1 timer and microsecond resolution +Timers::TimerSet<1, Timers::Resolution::micros> timerset; // create a TimerSet with 1 timer and microsecond resolution Timers::HandlerResult toggle_led() { digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED diff --git a/examples/full/full.ino b/examples/full/full.ino index 25f2558..20d0197 100644 --- a/examples/full/full.ino +++ b/examples/full/full.ino @@ -16,10 +16,10 @@ auto timerset = Timers::create_default(); // create a TimerSet with default sett Timers::TimerSet<> default_timerset; // same as above // create a TimerSet that can hold 1 concurrent task, with microsecond resolution -Timers::TimerSet<1, micros, delayMicroseconds> microtimerset; +Timers::TimerSet<1, Timers::Resolution::micros> microtimerset; // create a TimerSet that holds 16 tasks, with millisecond resolution -Timers::TimerSet<16, millis> t_timerset; +Timers::TimerSet<16, Timers::Resolution::millis> t_timerset; Timers::HandlerResult toggle_led() { digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED diff --git a/src/arduino-timer-cpp17.h b/src/arduino-timer-cpp17.h index 1d43343..f0decbe 100644 --- a/src/arduino-timer-cpp17.h +++ b/src/arduino-timer-cpp17.h @@ -88,15 +88,46 @@ struct Timer { using TimerHandle = std::optional>; +struct Resolution { + struct millis {}; + struct micros {}; +}; + template < size_t max_timers = TIMERSET_DEFAULT_TIMERS, // max number of timers - Timepoint (*time_func)() = millis, // time function for timer - void (*delay_func)(Timepoint) = delay // delay function corresponding to time_func + typename resolution = Resolution::millis // resolution of timers > class TimerSet { std::array timers; - inline + Timepoint + get_clock(Resolution::millis) { + return millis(); + } + + Timepoint + get_clock(Resolution::micros) { + return micros(); + } + + void + delay_until(Timepoint time, Resolution::millis) { + delay(time); + } + + void + delay_until(Timepoint time, Resolution::micros) { + unsigned int micros = time % 1000; + + time -= micros; + + delayMicroseconds(micros); + + if (time > 0) { + delay_until(time, Resolution::millis()); + } + } + void remove(TimerHandle handle) { @@ -112,14 +143,12 @@ class TimerSet { timer.repeat = 0; } - inline auto next_timer_slot() { return std::find_if(timers.begin(), timers.end(), [](Timer& t){ return !t.handler; }); } - inline TimerHandle add_timer(Timepoint start, Timepoint expires, Handler h, Timepoint repeat = 0) { @@ -141,14 +170,14 @@ class TimerSet { TimerHandle in(Timepoint delay, Handler h) { - return add_timer(time_func(), delay, h); + return add_timer(get_clock(resolution()), delay, h); } // Calls handler at time TimerHandle at(Timepoint time, Handler h) { - const Timepoint now = time_func(); + const Timepoint now = get_clock(resolution()); return add_timer(now, time - now, h); } @@ -156,14 +185,14 @@ class TimerSet { TimerHandle every(Timepoint interval, Handler h) { - return add_timer(time_func(), interval, h, interval); + return add_timer(get_clock(resolution()), interval, h, interval); } // Calls handler immediately and every interval units of time TimerHandle now_and_every(Timepoint interval, Handler h) { - const Timepoint now = time_func(); + const Timepoint now = get_clock(resolution()); return add_timer(now, now, h, interval); } @@ -197,7 +226,7 @@ class TimerSet { continue; } - Timepoint now = time_func(); + Timepoint now = get_clock(resolution()); Timepoint elapsed = now - timer.start; if (elapsed >= timer.expires) { @@ -221,7 +250,7 @@ class TimerSet { // compute lowest remaining time after all handlers have been executed // (some timers may have expired during handler execution) - const Timepoint now = time_func(); + const Timepoint now = get_clock(resolution()); for (auto& timer: timers) { if (!timer.handler) { @@ -239,13 +268,17 @@ class TimerSet { void tick_and_delay() { - delay_func(tick()); + Timepoint next = tick(); + + if (next > 0) { + delay_until(next, resolution()); + } } }; // create TimerSet with default settings -inline TimerSet<> +TimerSet<> create_default() { return TimerSet<>(); From 8c4ed44383445f58554c1d9ca7b6780e02cb0fde Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Mon, 10 Aug 2020 21:02:17 -0400 Subject: [PATCH 14/31] bug fixes --- src/arduino-timer-cpp17.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/arduino-timer-cpp17.h b/src/arduino-timer-cpp17.h index f0decbe..53320cf 100644 --- a/src/arduino-timer-cpp17.h +++ b/src/arduino-timer-cpp17.h @@ -55,7 +55,7 @@ using Timepoint = unsigned long; enum class TimerStatus { - complete, + completed, repeat, reschedule }; @@ -204,7 +204,7 @@ class TimerSet { return; } - auto timer = handle.value().get(); + auto& timer = handle.value().get(); if (!timer.handler) { return; @@ -233,7 +233,7 @@ class TimerSet { auto [ status, next ] = timer.handler(); switch (status) { - case TimerStatus::complete: + case TimerStatus::completed: remove(timer); break; case TimerStatus::repeat: From c2e807a7a8db80ebf154517d1155be55732858ba Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Tue, 11 Aug 2020 06:19:19 -0400 Subject: [PATCH 15/31] rename Resolution to Clock use dependency injection for Clock instead of tag dispatch --- README.md | 6 +- examples/blink/blink.ino | 2 +- examples/blink_micros/blink_micros.ino | 2 +- examples/full/full.ino | 12 +-- .../rollover-generic/rollover-generic.ino | 26 +++--- extras/tests/rollover/rollover.ino | 10 +- src/arduino-timer-cpp17.h | 93 ++++++++++--------- 7 files changed, 79 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index 32f0203..56eba5a 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,10 @@ Include the library and create a *TimerSet* instance. auto timerset = Timers::create_default(); ``` -Or using the *TimerSet* constructors for different timer limits / time resolution. +Or using the *TimerSet* constructors for different timer limits / time clocks. ```cpp -Timers::TimerSet<10> timerset; // 10 concurrent timers, using millis as resolution -Timers::TimerSet<10, Timers::Resolution::micros> microtimerset; // 10 concurrent timers, using micros as resolution +Timers::TimerSet<10> timerset; // 10 concurrent timers, using millisecond clock +Timers::TimerSet<10, Timers::Clock::micros> microtimerset; // 10 concurrent timers, using microsecond clock ``` Call *timerset*.**tick_and_delay()** in the ```loop``` function to execute handlers for any timers diff --git a/examples/blink/blink.ino b/examples/blink/blink.ino index 71d6c0d..9dc1930 100644 --- a/examples/blink/blink.ino +++ b/examples/blink/blink.ino @@ -11,7 +11,7 @@ auto timerset = Timers::create_default(); // create a TimerSet with default sett Timers::HandlerResult toggle_led() { digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED - return Timers::TimerStatus::repeat; + return Timers::TimerStatus::repeat; } void setup() { diff --git a/examples/blink_micros/blink_micros.ino b/examples/blink_micros/blink_micros.ino index 707a4fd..f9b24eb 100644 --- a/examples/blink_micros/blink_micros.ino +++ b/examples/blink_micros/blink_micros.ino @@ -7,7 +7,7 @@ #include -Timers::TimerSet<1, Timers::Resolution::micros> timerset; // create a TimerSet with 1 timer and microsecond resolution +Timers::TimerSet<1, Timers::Clock::micros> timerset; // create a TimerSet with 1 timer and microsecond clock Timers::HandlerResult toggle_led() { digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED diff --git a/examples/full/full.ino b/examples/full/full.ino index 20d0197..307ff98 100644 --- a/examples/full/full.ino +++ b/examples/full/full.ino @@ -3,7 +3,7 @@ * * Full example using the arduino-timer-cpp17 library. * Shows: - * - Setting a different number of tasks with microsecond resolution + * - Setting a different number of tasks with microsecond clock * - disabling a repeated function * - running a function after a delay * - cancelling a task @@ -15,11 +15,11 @@ auto timerset = Timers::create_default(); // create a TimerSet with default settings Timers::TimerSet<> default_timerset; // same as above -// create a TimerSet that can hold 1 concurrent task, with microsecond resolution -Timers::TimerSet<1, Timers::Resolution::micros> microtimerset; +// create a TimerSet that can hold 1 concurrent task, with microsecond clock +Timers::TimerSet<1, Timers::Clock::micros> microtimerset; -// create a TimerSet that holds 16 tasks, with millisecond resolution -Timers::TimerSet<16, Timers::Resolution::millis> t_timerset; +// create a TimerSet that holds 16 tasks, with millisecond clock +Timers::TimerSet<16, Timers::Clock::millis> t_timerset; Timers::HandlerResult toggle_led() { digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED @@ -67,7 +67,7 @@ void setup() { auto timer = timerset.every(500, toggle_led); timerset.cancel(timer); // this task is now cancelled, and will not run - // call print_message in 2 seconds, but with microsecond resolution + // call print_message in 2 seconds, but with microsecond clock microtimerset.in(2000000, [](){ return print_message("delayed two seconds using microseconds"); }); if (!microtimerset.in(5000, [](){ return print_message("never printed"); })) { diff --git a/extras/tests/rollover-generic/rollover-generic.ino b/extras/tests/rollover-generic/rollover-generic.ino index 66741fa..761bf18 100644 --- a/extras/tests/rollover-generic/rollover-generic.ino +++ b/extras/tests/rollover-generic/rollover-generic.ino @@ -2,15 +2,15 @@ Test timer rollover handling */ -#include +#include -unsigned long wrapping_millis(); +Timers::Timepoint wrapping_millis(); -Timer<1, wrapping_millis> timer; // this timer will wrap -auto _timer = timer_create_default(); // to count milliseconds +Timers::TimerSet<1, Timers::Clock::custom> timerset; // this timer will wrap +auto _timerset = Timers::create_default(); // to count milliseconds -unsigned long _millis = 0L; -unsigned long wrapping_millis() +Timers::Timepoint _millis = 0L; +Timers::Timepoint wrapping_millis() { // uses _millis controled by _timer // 6-second time loop starting at rollover - 3 seconds @@ -21,19 +21,19 @@ unsigned long wrapping_millis() void setup() { pinMode(LED_BUILTIN, OUTPUT); - _timer.every(1, [](void *) -> bool { + _timerset.every(1, [](){ ++_millis; // increase _millis every millisecond - return true; + return Timers::TimerStatus::repeat; }); - // should blink the led every second, regardless of wrapping - timer.every(1000, [](void *) -> bool { + // should blink the LED every second, regardless of wrapping + timerset.every(1000, [](){ digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); - return true; + return Timers::TimerStatus::repeat; }); } void loop() { - _timer.tick(); - timer.tick(); + _timerset.tick(); + timerset.tick(); } diff --git a/extras/tests/rollover/rollover.ino b/extras/tests/rollover/rollover.ino index 0753041..631ff5f 100644 --- a/extras/tests/rollover/rollover.ino +++ b/extras/tests/rollover/rollover.ino @@ -3,9 +3,9 @@ */ #include -#include +#include -auto timer = timer_create_default(); +auto timerset = Timers::create_default(); // https://arduino.stackexchange.com/questions/12587/how-can-i-handle-the-millis-rollover void set_millis(unsigned long ms) @@ -18,9 +18,9 @@ void set_millis(unsigned long ms) void setup() { pinMode(LED_BUILTIN, OUTPUT); - timer.every(1000, [](void *) -> bool { + timerset.every(1000, [](){ digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); - return true; + return Timers::TimerStatus::repeat; }); } @@ -28,5 +28,5 @@ void loop() { // 6-second time loop starting at rollover - 3 seconds if (millis() - (-3000) >= 6000) set_millis(-3000); - timer.tick(); + timerset.tick(); } diff --git a/src/arduino-timer-cpp17.h b/src/arduino-timer-cpp17.h index 53320cf..a9e998b 100644 --- a/src/arduino-timer-cpp17.h +++ b/src/arduino-timer-cpp17.h @@ -88,46 +88,57 @@ struct Timer { using TimerHandle = std::optional>; -struct Resolution { - struct millis {}; - struct micros {}; +struct Clock { + struct millis { + static + Timepoint + now() { + return ::millis(); + } + + static + void + delay(Timepoint until) { + ::delay(until); + } + }; + + struct micros { + static + Timepoint + now() { + return ::micros(); + } + + static + void + delay(Timepoint until) { + unsigned int micros = until % 1000; + + ::delayMicroseconds(micros); + ::delay(until - micros); + } + }; + + template < + Timers::Timepoint (*clock_func)() + > + struct custom { + static + Timepoint + now() { + return clock_func(); + } + }; }; template < size_t max_timers = TIMERSET_DEFAULT_TIMERS, // max number of timers - typename resolution = Resolution::millis // resolution of timers + typename clock = Clock::millis // clock for timers > class TimerSet { std::array timers; - Timepoint - get_clock(Resolution::millis) { - return millis(); - } - - Timepoint - get_clock(Resolution::micros) { - return micros(); - } - - void - delay_until(Timepoint time, Resolution::millis) { - delay(time); - } - - void - delay_until(Timepoint time, Resolution::micros) { - unsigned int micros = time % 1000; - - time -= micros; - - delayMicroseconds(micros); - - if (time > 0) { - delay_until(time, Resolution::millis()); - } - } - void remove(TimerHandle handle) { @@ -170,14 +181,14 @@ class TimerSet { TimerHandle in(Timepoint delay, Handler h) { - return add_timer(get_clock(resolution()), delay, h); + return add_timer(clock::now(), delay, h); } // Calls handler at time TimerHandle at(Timepoint time, Handler h) { - const Timepoint now = get_clock(resolution()); + const Timepoint now = clock::now(); return add_timer(now, time - now, h); } @@ -185,14 +196,14 @@ class TimerSet { TimerHandle every(Timepoint interval, Handler h) { - return add_timer(get_clock(resolution()), interval, h, interval); + return add_timer(clock::now(), interval, h, interval); } // Calls handler immediately and every interval units of time TimerHandle now_and_every(Timepoint interval, Handler h) { - const Timepoint now = get_clock(resolution()); + const Timepoint now = clock::now(); return add_timer(now, now, h, interval); } @@ -226,7 +237,7 @@ class TimerSet { continue; } - Timepoint now = get_clock(resolution()); + Timepoint now = clock::now(); Timepoint elapsed = now - timer.start; if (elapsed >= timer.expires) { @@ -250,7 +261,7 @@ class TimerSet { // compute lowest remaining time after all handlers have been executed // (some timers may have expired during handler execution) - const Timepoint now = get_clock(resolution()); + const Timepoint now = clock::now(); for (auto& timer: timers) { if (!timer.handler) { @@ -268,11 +279,7 @@ class TimerSet { void tick_and_delay() { - Timepoint next = tick(); - - if (next > 0) { - delay_until(next, resolution()); - } + clock::delay(tick()); } }; From d8dd5bf71d5507483ec2fab8177182c80cf99ce8 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Tue, 11 Aug 2020 06:48:16 -0400 Subject: [PATCH 16/31] fix bug where repeat was specified with no interval stored --- src/arduino-timer-cpp17.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/arduino-timer-cpp17.h b/src/arduino-timer-cpp17.h index a9e998b..4178079 100644 --- a/src/arduino-timer-cpp17.h +++ b/src/arduino-timer-cpp17.h @@ -248,8 +248,12 @@ class TimerSet { remove(timer); break; case TimerStatus::repeat: - timer.start = now; - timer.expires = timer.repeat; + if (timer.repeat > 0) { + timer.start = now; + timer.expires = timer.repeat; + } else { + remove(timer); + } break; case TimerStatus::reschedule: timer.start = now; From 5e45ea64a484e57f4654dcb1082567094f88689b Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Tue, 11 Aug 2020 06:48:26 -0400 Subject: [PATCH 17/31] update API docs --- README.md | 75 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 56eba5a..530177c 100644 --- a/README.md +++ b/README.md @@ -16,19 +16,19 @@ auto timerset = Timers::create_default(); Or using the *TimerSet* constructors for different timer limits / time clocks. ```cpp -Timers::TimerSet<10> timerset; // 10 concurrent timers, using millisecond clock -Timers::TimerSet<10, Timers::Clock::micros> microtimerset; // 10 concurrent timers, using microsecond clock +Timers::TimerSet<10> timerset; // 10 concurrent Timers, using millisecond clock +Timers::TimerSet<10, Timers::Clock::micros> microtimerset; // 10 concurrent Timers, using microsecond clock ``` -Call *timerset*.**tick_and_delay()** in the ```loop``` function to execute handlers for any timers -which have expired and then delay until the next scheduled timer expiration. +Call *timerset*.**tick_and_delay()** in the ```loop``` function to execute handlers for any Timers +which have expired and then delay until the next scheduled Timer expiration. ```cpp void loop() { timerset.tick_and_delay(); } ``` -Call *timerset*.**tick()** in the ```loop``` function to execute handlers for any timers +Call *timerset*.**tick()** in the ```loop``` function to execute handlers for any Timers which have expired, and then return so additional processing can be handled in the loop function. ```cpp void loop() { @@ -46,7 +46,7 @@ Timers::HandlerResult function_to_call() { Make a function to call (with an argument) when a *Timer* expires. ```cpp Timers::HandlerResult function_to_call_with_arg(int value) { - return Timers::TimerStatus::completed; // to stop the timer - 'repeat' to repeat the action + return Timers::TimerStatus::completed; // to stop the Timer - 'repeat' to repeat the action } ``` @@ -101,39 +101,52 @@ timerset.cancel(timer); ```cpp /* Constructors */ -/* Create a timer object with default settings: - millis resolution, TIMER_MAX_TASKS (=16) task slots, T = void * +/* Create a TimerSet object with default settings: + millisecond clock, TIMERSET_DEFAULT_TIMERS (=16) Timer slots */ -Timer<> timer_create_default(); // auto timer = timer_create_default(); +Timers::TimerSet<> Timers::create_default() // auto timerset = Timers::create_default(); -/* Create a timer with max_tasks slots and time_func resolution */ -Timer timer; -Timer<> timer; // Equivalent to: auto timer = timer_create_default() -Timer<10> timer; // Timer with 10 task slots -Timer<10, micros> timer; // timer with 10 task slots and microsecond resolution -Timer<10, micros, int> timer; // timer with 10 task slots, microsecond resolution, and handler argument type int +/* Create a TimerSet with max_timers slots and millisecond clock */ +Timers::TimerSet() -/* Signature for handler functions - T = void * by default */ -bool handler(T argument); +Timers::TimerSet<> timerset; // Equivalent to: auto timerset = Timers::create_default(); +Timers::TimerSet<10> timerset; // TimerSet with 10 Timer slots +Timers::TimerSet<10, Timers::Clock::micros> timerset; // TimerSet with 10 Timer slots and microsecond clock -/* Timer Methods */ -/* Ticks the timer forward, returns the ticks until next event, or 0 if none */ -unsigned long tick(); // call this function in loop() +/* Handler function signature; returns a HandlerResult */ +Timers::HandlerResult handler() // declared as Timers::Handler -/* Calls handler with opaque as argument in delay units of time */ -Timer<>::Task -in(unsigned long delay, handler_t handler, T opaque = T()); +/* HandlerResult contains a TimerStatus, and optional 'next' Timepoint */ +/* (in handler function) */ +return Timers::TimerStatus::completed; // remove Timer from TimerSet +return Timers::TimerStatus::repeat; // repeat Timer at previously-set interval +return { Timers::TimerStatus::reschedule, 3000 }; // repeat Timer at new interval of 3000 clock ticks -/* Calls handler with opaque as argument at time */ -Timer<>::Task -at(unsigned long time, handler_t handler, T opaque = T()); +/* TimerSet Methods */ +// Ticks the TimerSet forward, returns the ticks until next event, or 0 if none +Timers::Timepoint tick(); // call this function in loop() -/* Calls handler with opaque as argument every interval units of time */ -Timer<>::Task -every(unsigned long interval, handler_t handler, T opaque = T()); +// Ticks the TimerSet forward, and delays until the next event +void tick_and_delay(); // call this function in loop() -/* Cancel a timer task */ -void cancel(Timer<>::Task &task); +/* Calls handler in delay units of time */ +Timers::TimerHandle +in(Timers::Timepoint delay, Timers::Handler handler); + +/* Calls handler at time */ +Timers::TimerHandle +at(Timers::Timepoint time, Timers::Handler handler); + +/* Calls handler every interval units of time */ +Timers::TimerHandle +every(Timers::Timepoint interval, Timers::Handler handler); + +/* Calls handler now and every interval units of time */ +Timers::TimerHandle +now_and_every(Timers::Timepoint interval, Timers::Handler handler); + +/* Cancel a Timer */ +void cancel(Timers::TimerHandle timer); ``` ### Installation From 8d057b166d24347eff0da35487af53f8820243e7 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Tue, 11 Aug 2020 06:52:25 -0400 Subject: [PATCH 18/31] more README cleanups --- README.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 530177c..2d9a0c4 100644 --- a/README.md +++ b/README.md @@ -151,9 +151,7 @@ void cancel(Timers::TimerHandle timer); ### Installation -[Check out the instructions](https://www.arduino.cc/en/Guide/Libraries) from Arduino. - -**OR** copy **src/arduino-timer.h** into your project folder *(you won't get managed updates this way)*. +Copy **src/arduino-timer-cpp17.h** into your project folder. ### Examples @@ -162,13 +160,13 @@ Found in the [**examples**](examples) folder. The simplest example, blinking an LED every second *(from examples/blink)*: ```cpp -#include +#include auto timerset = Timers::create_default(); // create a timerset with default settings Timers::HandlerResult toggle_led() { digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED - return Timers::TimerStatus::repeat; // keep timer active? true + return Timers::TimerStatus::repeat; } void setup() { @@ -199,5 +197,3 @@ functions return a TimerHandle which evaluates to ```false``` if the TimerSet is A *TimerHandle* value is valid only for the TimerSet that created it, and only for the lifetime of that timer. Change the number of concurrent timers using the *Timer* constructors. Save memory by reducing the number, increase memory use by having more. The default is **TIMERSET_DEFAULT_TIMERS** which is currently 16. - -If you find this project useful, [consider becoming a sponsor.](https://github.com/sponsors/contrem) From 98b32d2d4057486063be1641a1e2415564e19b1e Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Tue, 11 Aug 2020 07:24:23 -0400 Subject: [PATCH 19/31] add note about board/toolchain support --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2d9a0c4..db9a3e1 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ Simple *non-blocking* timer library for calling functions **in / at / every** specified units of time. Supports millis, micros, time rollover, and compile-time configurable number of timers. This library was inspired by [Michael Contreras' arduino-timer library](https://github.com/contrem/arduino-timer), but has been rewritten to make use of 'modern C++' types and -functionality. As a result this library requires that the Arduino IDE toolchain be manually configured for "gnu++17" mode. +functionality. As a result this library requires that the Arduino IDE toolchain be manually configured for "gnu++17" mode. As of the 1.8.13 version of the Arduino IDE this has +only been tested with the SAMD-based boards; the toolchain for the MegaAVR-based boards does not provide the C++ standard library so this timer library cannot be used there. ### Use It From dcd99f69f04b26a675d2356292f66ebfde3f4b6a Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Fri, 14 Aug 2020 07:42:26 -0400 Subject: [PATCH 20/31] Correct errors in microsecond delay() function --- src/arduino-timer-cpp17.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/arduino-timer-cpp17.h b/src/arduino-timer-cpp17.h index 4178079..52c23ce 100644 --- a/src/arduino-timer-cpp17.h +++ b/src/arduino-timer-cpp17.h @@ -114,9 +114,11 @@ struct Clock { void delay(Timepoint until) { unsigned int micros = until % 1000; + until -= micros; + until /= 1000; ::delayMicroseconds(micros); - ::delay(until - micros); + ::delay(until); } }; From 8685aaf1bcafacbd9a9bf5a9dfafcfe18cf151ac Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Sat, 15 Aug 2020 08:21:31 -0400 Subject: [PATCH 21/31] add rescheduling support --- README.md | 8 +++++- examples/full/full.ino | 4 +++ keywords.txt | 2 ++ src/arduino-timer-cpp17.h | 53 +++++++++++++++++++++++++++++++++------ 4 files changed, 58 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index db9a3e1..d359a78 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,13 @@ Timers::TimerHandle now_and_every(Timers::Timepoint interval, Timers::Handler handler); /* Cancel a Timer */ -void cancel(Timers::TimerHandle timer); +Timers::TimerHandle cancel(Timers::TimerHandle timer); + +/* Reschedules handler to be called in delay units of time */ +Timers::TimerHandle reschedule_in(Timers::TimerHandle handle, Timers::Timepoint delay); + +/* Reschedules handler to be called at time */ +Timers::TimerHandle reschedule_at(Timers::TimerHandle handle, Timers::Timepoint when); ``` ### Installation diff --git a/examples/full/full.ino b/examples/full/full.ino index 307ff98..8aac061 100644 --- a/examples/full/full.ino +++ b/examples/full/full.ino @@ -60,6 +60,10 @@ void setup() { // call the print_message function in five seconds t_timerset.in(5000, [](){ return print_message("delayed five seconds"); }); + // call the print_message function in fifteen seconds by scheduling and then rescheduling + auto resched_timer = t_timerset.in(5000, [](){ return print_message("delayed fifteen seconds"); }); + t_timerset.reschedule_in(resched_timer, 15000); + // call the print_message function at time + 10 seconds t_timerset.at(millis() + 10000, [](){ return print_message("call at millis() + 10 seconds"); }); diff --git a/keywords.txt b/keywords.txt index 901f903..d2c407c 100644 --- a/keywords.txt +++ b/keywords.txt @@ -24,6 +24,8 @@ now_and_every KEYWORD2 tick KEYWORD2 cancel KEYWORD2 tick_and_delay KEYWORD2 +reschedule_at KEYWORD2 +reschedule_in KEYWORD2 ####################################### # Instances (KEYWORD2) diff --git a/src/arduino-timer-cpp17.h b/src/arduino-timer-cpp17.h index 52c23ce..253f5de 100644 --- a/src/arduino-timer-cpp17.h +++ b/src/arduino-timer-cpp17.h @@ -2,6 +2,7 @@ arduino-timer - library for delaying function calls Copyright (c) 2018, Michael Contreras + Copyright (c) 2020, Kevin P. Fleming All rights reserved. Redistribution and use in source and binary forms, with or without @@ -178,6 +179,25 @@ class TimerSet { } } + // Reschedules handler to be called in delay units of time + TimerHandle + reschedule_timer(TimerHandle handle, Timepoint delay) + { + if (!handle) { + return handle; + } + + auto& timer = handle.value().get(); + + if (!timer.handler) { + return handle; + } + + timer.expires = delay; + + return handle; + } + public: // Calls handler in delay units of time TimerHandle @@ -188,10 +208,10 @@ class TimerSet { // Calls handler at time TimerHandle - at(Timepoint time, Handler h) + at(Timepoint when, Handler h) { - const Timepoint now = clock::now(); - return add_timer(now, time - now, h); + Timepoint now = clock::now(); + return add_timer(now, when - now, h); } // Calls handler every interval units of time @@ -205,25 +225,42 @@ class TimerSet { TimerHandle now_and_every(Timepoint interval, Handler h) { - const Timepoint now = clock::now(); + Timepoint now = clock::now(); return add_timer(now, now, h, interval); } // Cancels timer - void + TimerHandle cancel(TimerHandle handle) { if (!handle) { - return; + return handle; } auto& timer = handle.value().get(); if (!timer.handler) { - return; + return handle; } remove(timer); + + return handle; + } + + // Reschedules handler to be called in delay units of time + TimerHandle + reschedule_in(TimerHandle handle, Timepoint delay) + { + return reschedule_timer(handle, delay); + } + + // Reschedules handler to be called at time + TimerHandle + reschedule_at(TimerHandle handle, Timepoint when) + { + Timepoint now = clock::now(); + return reschedule_timer(handle, when - now); } // Ticks the timerset forward - call this function in loop() @@ -267,7 +304,7 @@ class TimerSet { // compute lowest remaining time after all handlers have been executed // (some timers may have expired during handler execution) - const Timepoint now = clock::now(); + Timepoint now = clock::now(); for (auto& timer: timers) { if (!timer.handler) { From 732185e4790556a66386811856f5692969f95567 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Sun, 16 Aug 2020 08:43:32 -0400 Subject: [PATCH 22/31] Correct logic error in rescheduling functions --- src/arduino-timer-cpp17.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/arduino-timer-cpp17.h b/src/arduino-timer-cpp17.h index 253f5de..580814b 100644 --- a/src/arduino-timer-cpp17.h +++ b/src/arduino-timer-cpp17.h @@ -181,7 +181,7 @@ class TimerSet { // Reschedules handler to be called in delay units of time TimerHandle - reschedule_timer(TimerHandle handle, Timepoint delay) + reschedule_timer(TimerHandle handle, Timepoint start, Timepoint expires) { if (!handle) { return handle; @@ -193,7 +193,8 @@ class TimerSet { return handle; } - timer.expires = delay; + timer.start = start; + timer.expires = expires; return handle; } @@ -252,7 +253,7 @@ class TimerSet { TimerHandle reschedule_in(TimerHandle handle, Timepoint delay) { - return reschedule_timer(handle, delay); + return reschedule_timer(handle, clock::now(), delay); } // Reschedules handler to be called at time @@ -260,7 +261,7 @@ class TimerSet { reschedule_at(TimerHandle handle, Timepoint when) { Timepoint now = clock::now(); - return reschedule_timer(handle, when - now); + return reschedule_timer(handle, now, when - now); } // Ticks the timerset forward - call this function in loop() From 9fae7e2203d3ef6140d0e732390138df79a82efd Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Sun, 16 Aug 2020 09:22:15 -0400 Subject: [PATCH 23/31] Rename header to .hpp to ensure editors recognize it as C++ Reformat all C++ source files --- README.md | 6 +- examples/blink/blink.ino | 14 +- examples/blink_micros/blink_micros.ino | 14 +- examples/blink_print/blink_print.ino | 26 +- examples/full/full.ino | 80 ++--- .../rollover-generic/rollover-generic.ino | 24 +- extras/tests/rollover/rollover.ino | 15 +- library.properties | 2 +- src/arduino-timer-cpp17.h | 338 ------------------ src/arduino-timer-cpp17.hpp | 338 ++++++++++++++++++ 10 files changed, 430 insertions(+), 427 deletions(-) delete mode 100644 src/arduino-timer-cpp17.h create mode 100644 src/arduino-timer-cpp17.hpp diff --git a/README.md b/README.md index d359a78..e6f98e4 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ only been tested with the SAMD-based boards; the toolchain for the MegaAVR-based Include the library and create a *TimerSet* instance. ```cpp -#include +#include auto timerset = Timers::create_default(); ``` @@ -158,7 +158,7 @@ Timers::TimerHandle reschedule_at(Timers::TimerHandle handle, Timers::Timepoint ### Installation -Copy **src/arduino-timer-cpp17.h** into your project folder. +Copy **src/arduino-timer-cpp17.hpp** into your project folder. ### Examples @@ -167,7 +167,7 @@ Found in the [**examples**](examples) folder. The simplest example, blinking an LED every second *(from examples/blink)*: ```cpp -#include +#include auto timerset = Timers::create_default(); // create a timerset with default settings diff --git a/examples/blink/blink.ino b/examples/blink/blink.ino index 9dc1930..f164dcf 100644 --- a/examples/blink/blink.ino +++ b/examples/blink/blink.ino @@ -5,22 +5,22 @@ * */ -#include +#include auto timerset = Timers::create_default(); // create a TimerSet with default settings Timers::HandlerResult toggle_led() { - digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED - return Timers::TimerStatus::repeat; + digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED + return Timers::TimerStatus::repeat; } void setup() { - pinMode(LED_BUILTIN, OUTPUT); // set LED pin to OUTPUT + pinMode(LED_BUILTIN, OUTPUT); // set LED pin to OUTPUT - // call the toggle_led function every 1000 millis (1 second) - timerset.every(1000, toggle_led); + // call the toggle_led function every 1000 millis (1 second) + timerset.every(1000, toggle_led); } void loop() { - timerset.tick_and_delay(); // tick the timer + timerset.tick_and_delay(); // tick the timer } diff --git a/examples/blink_micros/blink_micros.ino b/examples/blink_micros/blink_micros.ino index f9b24eb..f85e541 100644 --- a/examples/blink_micros/blink_micros.ino +++ b/examples/blink_micros/blink_micros.ino @@ -5,22 +5,22 @@ * */ -#include +#include Timers::TimerSet<1, Timers::Clock::micros> timerset; // create a TimerSet with 1 timer and microsecond clock Timers::HandlerResult toggle_led() { - digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED - return Timers::TimerStatus::repeat; + digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED + return Timers::TimerStatus::repeat; } void setup() { - pinMode(LED_BUILTIN, OUTPUT); // set LED pin to OUTPUT + pinMode(LED_BUILTIN, OUTPUT); // set LED pin to OUTPUT - // call the toggle_led function every 1000000 micros (1 second) - timerset.every(1000000, toggle_led); + // call the toggle_led function every 1000000 micros (1 second) + timerset.every(1000000, toggle_led); } void loop() { - timerset.tick_and_delay(); // tick the timer + timerset.tick_and_delay(); // tick the timer } diff --git a/examples/blink_print/blink_print.ino b/examples/blink_print/blink_print.ino index bc2098b..af21dcb 100644 --- a/examples/blink_print/blink_print.ino +++ b/examples/blink_print/blink_print.ino @@ -6,32 +6,32 @@ * */ -#include +#include auto timerset = Timers::create_default(); // create a TimerSet with default settings Timers::HandlerResult toggle_led() { - digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED - return Timers::TimerStatus::repeat; + digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED + return Timers::TimerStatus::repeat; } Timers::HandlerResult print_message() { - Serial.print("print_message: Called at: "); - Serial.println(millis()); - return Timers::TimerStatus::repeat; + Serial.print("print_message: Called at: "); + Serial.println(millis()); + return Timers::TimerStatus::repeat; } void setup() { - Serial.begin(9600); - pinMode(LED_BUILTIN, OUTPUT); // set LED pin to OUTPUT + Serial.begin(9600); + pinMode(LED_BUILTIN, OUTPUT); // set LED pin to OUTPUT - // call the toggle_led function every 500 millis (half second) - timerset.every(500, toggle_led); + // call the toggle_led function every 500 millis (half second) + timerset.every(500, toggle_led); - // call the print_message function every 1000 millis (1 second) - timerset.every(1000, print_message); + // call the print_message function every 1000 millis (1 second) + timerset.every(1000, print_message); } void loop() { - timerset.tick_and_delay(); // tick the timer + timerset.tick_and_delay(); // tick the timer } diff --git a/examples/full/full.ino b/examples/full/full.ino index 8aac061..b7b22b9 100644 --- a/examples/full/full.ino +++ b/examples/full/full.ino @@ -10,7 +10,7 @@ * */ -#include +#include auto timerset = Timers::create_default(); // create a TimerSet with default settings Timers::TimerSet<> default_timerset; // same as above @@ -22,66 +22,66 @@ Timers::TimerSet<1, Timers::Clock::micros> microtimerset; Timers::TimerSet<16, Timers::Clock::millis> t_timerset; Timers::HandlerResult toggle_led() { - digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED - return Timers::TimerStatus::repeat; + digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED + return Timers::TimerStatus::repeat; } Timers::HandlerResult print_message(const char *message) { - Serial.print("print_message: "); - Serial.println(message); - return Timers::TimerStatus::repeat; + Serial.print("print_message: "); + Serial.println(message); + return Timers::TimerStatus::repeat; } size_t repeat_count = 1; Timers::HandlerResult repeat_x_times(size_t limit) { - Serial.print("repeat_x_times: "); - Serial.print(repeat_count); - Serial.print("/"); - Serial.println(limit); + Serial.print("repeat_x_times: "); + Serial.print(repeat_count); + Serial.print("/"); + Serial.println(limit); - // remove this task after limit reached - return ++repeat_count <= limit ? Timers::TimerStatus::repeat : Timers::TimerStatus::completed; + // remove this task after limit reached + return ++repeat_count <= limit ? Timers::TimerStatus::repeat : Timers::TimerStatus::completed; } void setup() { - Serial.begin(9600); - pinMode(LED_BUILTIN, OUTPUT); // set LED pin to OUTPUT + Serial.begin(9600); + pinMode(LED_BUILTIN, OUTPUT); // set LED pin to OUTPUT - // call the toggle_led function every 500 millis (half second) - timerset.every(500, toggle_led); + // call the toggle_led function every 500 millis (half second) + timerset.every(500, toggle_led); - // call the repeat_x_times function every 1000 millis (1 second) - timerset.every(1000, [](){ return repeat_x_times(10); }); + // call the repeat_x_times function every 1000 millis (1 second) + timerset.every(1000, [](){ return repeat_x_times(10); }); - // call the print_message function every 1000 millis (1 second), - // passing it an argument string - t_timerset.every(1000, [](){ return print_message("called every second"); }); + // call the print_message function every 1000 millis (1 second), + // passing it an argument string + t_timerset.every(1000, [](){ return print_message("called every second"); }); - // call the print_message function in five seconds - t_timerset.in(5000, [](){ return print_message("delayed five seconds"); }); + // call the print_message function in five seconds + t_timerset.in(5000, [](){ return print_message("delayed five seconds"); }); - // call the print_message function in fifteen seconds by scheduling and then rescheduling - auto resched_timer = t_timerset.in(5000, [](){ return print_message("delayed fifteen seconds"); }); - t_timerset.reschedule_in(resched_timer, 15000); + // call the print_message function in fifteen seconds by scheduling and then rescheduling + auto resched_timer = t_timerset.in(5000, [](){ return print_message("delayed fifteen seconds"); }); + t_timerset.reschedule_in(resched_timer, 15000); - // call the print_message function at time + 10 seconds - t_timerset.at(millis() + 10000, [](){ return print_message("call at millis() + 10 seconds"); }); + // call the print_message function at time + 10 seconds + t_timerset.at(millis() + 10000, [](){ return print_message("call at millis() + 10 seconds"); }); - // call the toggle_led function every 500 millis (half-second) - auto timer = timerset.every(500, toggle_led); - timerset.cancel(timer); // this task is now cancelled, and will not run + // call the toggle_led function every 500 millis (half-second) + auto timer = timerset.every(500, toggle_led); + timerset.cancel(timer); // this task is now cancelled, and will not run - // call print_message in 2 seconds, but with microsecond clock - microtimerset.in(2000000, [](){ return print_message("delayed two seconds using microseconds"); }); + // call print_message in 2 seconds, but with microsecond clock + microtimerset.in(2000000, [](){ return print_message("delayed two seconds using microseconds"); }); - if (!microtimerset.in(5000, [](){ return print_message("never printed"); })) { - /* this fails because we created microtimerset with only 1 concurrent timer slot */ - Serial.println("Failed to add microsecond event - timer full"); - } + if (!microtimerset.in(5000, [](){ return print_message("never printed"); })) { + /* this fails because we created microtimerset with only 1 concurrent timer slot */ + Serial.println("Failed to add microsecond event - timer full"); + } } void loop() { - timerset.tick(); - t_timerset.tick(); - microtimerset.tick(); + timerset.tick(); + t_timerset.tick(); + microtimerset.tick(); } diff --git a/extras/tests/rollover-generic/rollover-generic.ino b/extras/tests/rollover-generic/rollover-generic.ino index 761bf18..6b96b2f 100644 --- a/extras/tests/rollover-generic/rollover-generic.ino +++ b/extras/tests/rollover-generic/rollover-generic.ino @@ -1,8 +1,8 @@ /* - Test timer rollover handling - */ + Test timer rollover handling +*/ -#include +#include Timers::Timepoint wrapping_millis(); @@ -21,16 +21,18 @@ Timers::Timepoint wrapping_millis() void setup() { pinMode(LED_BUILTIN, OUTPUT); - _timerset.every(1, [](){ - ++_millis; // increase _millis every millisecond - return Timers::TimerStatus::repeat; - }); + _timerset.every(1, []() + { + ++_millis; // increase _millis every millisecond + return Timers::TimerStatus::repeat; + }); // should blink the LED every second, regardless of wrapping - timerset.every(1000, [](){ - digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); - return Timers::TimerStatus::repeat; - }); + timerset.every(1000, []() + { + digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); + return Timers::TimerStatus::repeat; + }); } void loop() { diff --git a/extras/tests/rollover/rollover.ino b/extras/tests/rollover/rollover.ino index 631ff5f..696ee08 100644 --- a/extras/tests/rollover/rollover.ino +++ b/extras/tests/rollover/rollover.ino @@ -1,9 +1,9 @@ /* - Test timer rollover handling - */ + Test timer rollover handling +*/ #include -#include +#include auto timerset = Timers::create_default(); @@ -18,10 +18,11 @@ void set_millis(unsigned long ms) void setup() { pinMode(LED_BUILTIN, OUTPUT); - timerset.every(1000, [](){ - digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); - return Timers::TimerStatus::repeat; - }); + timerset.every(1000, []() + { + digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); + return Timers::TimerStatus::repeat; + }); } void loop() { diff --git a/library.properties b/library.properties index 6516639..3d1c3df 100644 --- a/library.properties +++ b/library.properties @@ -8,4 +8,4 @@ paragraph=Simple non-blocking timer library for calling functions in / at / ever category=Timing url=https://github.com/kpfleming/arduino-timer-cpp17 architectures=* -includes=arduino-timer-cpp17.h +includes=arduino-timer-cpp17.hpp diff --git a/src/arduino-timer-cpp17.h b/src/arduino-timer-cpp17.h deleted file mode 100644 index 580814b..0000000 --- a/src/arduino-timer-cpp17.h +++ /dev/null @@ -1,338 +0,0 @@ -/** - arduino-timer - library for delaying function calls - - Copyright (c) 2018, Michael Contreras - Copyright (c) 2020, Kevin P. Fleming - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include - -#undef max -#undef min - -#include -#include -#include -#include -#include - -#ifndef TIMERSET_DEFAULT_TIMERS - #define TIMERSET_DEFAULT_TIMERS 0x10 -#endif - -namespace Timers { - -using Timepoint = unsigned long; - -enum class TimerStatus - { - completed, - repeat, - reschedule - }; - -struct HandlerResult { - TimerStatus status; - Timepoint next; - - // must be constructed with at least a status, but can be constructed - // with status and next - HandlerResult() = delete; - HandlerResult(TimerStatus status) : status(status) {} - HandlerResult(TimerStatus status, Timepoint next) : status(status), next(next) {} -}; - -using Handler = std::function; - -struct Timer { - Handler handler; - Timepoint start; // when timer was added (or repeat execution began) - Timepoint expires; // when the timer expires - Timepoint repeat; // default repeat interval - - // ensure that these objects will never be copied or moved - // (this could only happen by accident) - Timer() = default; - Timer(const Timer&) = delete; - Timer& operator=(const Timer&) = delete; -}; - -using TimerHandle = std::optional>; - -struct Clock { - struct millis { - static - Timepoint - now() { - return ::millis(); - } - - static - void - delay(Timepoint until) { - ::delay(until); - } - }; - - struct micros { - static - Timepoint - now() { - return ::micros(); - } - - static - void - delay(Timepoint until) { - unsigned int micros = until % 1000; - until -= micros; - until /= 1000; - - ::delayMicroseconds(micros); - ::delay(until); - } - }; - - template < - Timers::Timepoint (*clock_func)() - > - struct custom { - static - Timepoint - now() { - return clock_func(); - } - }; -}; - -template < - size_t max_timers = TIMERSET_DEFAULT_TIMERS, // max number of timers - typename clock = Clock::millis // clock for timers - > -class TimerSet { - std::array timers; - - void - remove(TimerHandle handle) - { - if (!handle) { - return; - } - - auto& timer = handle.value().get(); - - timer.handler = Handler(); - timer.start = 0; - timer.expires = 0; - timer.repeat = 0; - } - - auto - next_timer_slot() - { - return std::find_if(timers.begin(), timers.end(), [](Timer& t){ return !t.handler; }); - } - - TimerHandle - add_timer(Timepoint start, Timepoint expires, Handler h, Timepoint repeat = 0) - { - if (auto it = next_timer_slot(); it != timers.end()) { - it->handler = h; - it->start = start; - it->expires = expires; - it->repeat = repeat; - - return TimerHandle(*it); - } - else { - return TimerHandle(); - } - } - - // Reschedules handler to be called in delay units of time - TimerHandle - reschedule_timer(TimerHandle handle, Timepoint start, Timepoint expires) - { - if (!handle) { - return handle; - } - - auto& timer = handle.value().get(); - - if (!timer.handler) { - return handle; - } - - timer.start = start; - timer.expires = expires; - - return handle; - } - -public: - // Calls handler in delay units of time - TimerHandle - in(Timepoint delay, Handler h) - { - return add_timer(clock::now(), delay, h); - } - - // Calls handler at time - TimerHandle - at(Timepoint when, Handler h) - { - Timepoint now = clock::now(); - return add_timer(now, when - now, h); - } - - // Calls handler every interval units of time - TimerHandle - every(Timepoint interval, Handler h) - { - return add_timer(clock::now(), interval, h, interval); - } - - // Calls handler immediately and every interval units of time - TimerHandle - now_and_every(Timepoint interval, Handler h) - { - Timepoint now = clock::now(); - return add_timer(now, now, h, interval); - } - - // Cancels timer - TimerHandle - cancel(TimerHandle handle) - { - if (!handle) { - return handle; - } - - auto& timer = handle.value().get(); - - if (!timer.handler) { - return handle; - } - - remove(timer); - - return handle; - } - - // Reschedules handler to be called in delay units of time - TimerHandle - reschedule_in(TimerHandle handle, Timepoint delay) - { - return reschedule_timer(handle, clock::now(), delay); - } - - // Reschedules handler to be called at time - TimerHandle - reschedule_at(TimerHandle handle, Timepoint when) - { - Timepoint now = clock::now(); - return reschedule_timer(handle, now, when - now); - } - - // Ticks the timerset forward - call this function in loop() - // returns Timepoint of next timer expiration */ - Timepoint - tick() - { - Timepoint next_expiration = std::numeric_limits::max(); - - // execute handlers for any timers which have expired - for (auto& timer: timers) { - if (!timer.handler) { - continue; - } - - Timepoint now = clock::now(); - Timepoint elapsed = now - timer.start; - - if (elapsed >= timer.expires) { - auto [ status, next ] = timer.handler(); - - switch (status) { - case TimerStatus::completed: - remove(timer); - break; - case TimerStatus::repeat: - if (timer.repeat > 0) { - timer.start = now; - timer.expires = timer.repeat; - } else { - remove(timer); - } - break; - case TimerStatus::reschedule: - timer.start = now; - timer.expires = next; - break; - } - } - } - - // compute lowest remaining time after all handlers have been executed - // (some timers may have expired during handler execution) - Timepoint now = clock::now(); - - for (auto& timer: timers) { - if (!timer.handler) { - continue; - } - - Timepoint remaining = timer.expires - (now - timer.start); - next_expiration = remaining < next_expiration ? remaining : next_expiration; - } - - return next_expiration == std::numeric_limits::max() ? 0 : next_expiration; - } - - // Ticks the timerset forward, then delays until next timer is due - void - tick_and_delay() - { - clock::delay(tick()); - } -}; - - -// create TimerSet with default settings -TimerSet<> -create_default() -{ - return TimerSet<>(); -} - -}; diff --git a/src/arduino-timer-cpp17.hpp b/src/arduino-timer-cpp17.hpp new file mode 100644 index 0000000..b43ad66 --- /dev/null +++ b/src/arduino-timer-cpp17.hpp @@ -0,0 +1,338 @@ +/** + arduino-timer - library for delaying function calls + + Copyright (c) 2018, Michael Contreras + Copyright (c) 2020, Kevin P. Fleming + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include + +#undef max +#undef min + +#include +#include +#include +#include +#include + +#ifndef TIMERSET_DEFAULT_TIMERS +#define TIMERSET_DEFAULT_TIMERS 0x10 +#endif + +namespace Timers { + +using Timepoint = unsigned long; + +enum class TimerStatus + { + completed, + repeat, + reschedule + }; + +struct HandlerResult { + TimerStatus status; + Timepoint next; + + // must be constructed with at least a status, but can be constructed + // with status and next + HandlerResult() = delete; + HandlerResult(TimerStatus status) : status(status) {} + HandlerResult(TimerStatus status, Timepoint next) : status(status), next(next) {} +}; + +using Handler = std::function; + +struct Timer { + Handler handler; + Timepoint start; // when timer was added (or repeat execution began) + Timepoint expires; // when the timer expires + Timepoint repeat; // default repeat interval + + // ensure that these objects will never be copied or moved + // (this could only happen by accident) + Timer() = default; + Timer(const Timer&) = delete; + Timer& operator=(const Timer&) = delete; +}; + +using TimerHandle = std::optional>; + +struct Clock { + struct millis { + static + Timepoint + now() { + return ::millis(); + } + + static + void + delay(Timepoint until) { + ::delay(until); + } + }; + + struct micros { + static + Timepoint + now() { + return ::micros(); + } + + static + void + delay(Timepoint until) { + unsigned int micros = until % 1000; + until -= micros; + until /= 1000; + + ::delayMicroseconds(micros); + ::delay(until); + } + }; + + template < + Timers::Timepoint (*clock_func)() + > + struct custom { + static + Timepoint + now() { + return clock_func(); + } + }; +}; + +template < + size_t max_timers = TIMERSET_DEFAULT_TIMERS, // max number of timers + typename clock = Clock::millis // clock for timers + > +class TimerSet { + std::array timers; + + void + remove(TimerHandle handle) + { + if (!handle) { + return; + } + + auto& timer = handle.value().get(); + + timer.handler = Handler(); + timer.start = 0; + timer.expires = 0; + timer.repeat = 0; + } + + auto + next_timer_slot() + { + return std::find_if(timers.begin(), timers.end(), [](Timer& t){ return !t.handler; }); + } + + TimerHandle + add_timer(Timepoint start, Timepoint expires, Handler h, Timepoint repeat = 0) + { + if (auto it = next_timer_slot(); it != timers.end()) { + it->handler = h; + it->start = start; + it->expires = expires; + it->repeat = repeat; + + return TimerHandle(*it); + } + else { + return TimerHandle(); + } + } + + // Reschedules handler to be called in delay units of time + TimerHandle + reschedule_timer(TimerHandle handle, Timepoint start, Timepoint expires) + { + if (!handle) { + return handle; + } + + auto& timer = handle.value().get(); + + if (!timer.handler) { + return handle; + } + + timer.start = start; + timer.expires = expires; + + return handle; + } + +public: + // Calls handler in delay units of time + TimerHandle + in(Timepoint delay, Handler h) + { + return add_timer(clock::now(), delay, h); + } + + // Calls handler at time + TimerHandle + at(Timepoint when, Handler h) + { + Timepoint now = clock::now(); + return add_timer(now, when - now, h); + } + + // Calls handler every interval units of time + TimerHandle + every(Timepoint interval, Handler h) + { + return add_timer(clock::now(), interval, h, interval); + } + + // Calls handler immediately and every interval units of time + TimerHandle + now_and_every(Timepoint interval, Handler h) + { + Timepoint now = clock::now(); + return add_timer(now, now, h, interval); + } + + // Cancels timer + TimerHandle + cancel(TimerHandle handle) + { + if (!handle) { + return handle; + } + + auto& timer = handle.value().get(); + + if (!timer.handler) { + return handle; + } + + remove(timer); + + return handle; + } + + // Reschedules handler to be called in delay units of time + TimerHandle + reschedule_in(TimerHandle handle, Timepoint delay) + { + return reschedule_timer(handle, clock::now(), delay); + } + + // Reschedules handler to be called at time + TimerHandle + reschedule_at(TimerHandle handle, Timepoint when) + { + Timepoint now = clock::now(); + return reschedule_timer(handle, now, when - now); + } + + // Ticks the timerset forward - call this function in loop() + // returns Timepoint of next timer expiration */ + Timepoint + tick() + { + Timepoint next_expiration = std::numeric_limits::max(); + + // execute handlers for any timers which have expired + for (auto& timer: timers) { + if (!timer.handler) { + continue; + } + + Timepoint now = clock::now(); + Timepoint elapsed = now - timer.start; + + if (elapsed >= timer.expires) { + auto [ status, next ] = timer.handler(); + + switch (status) { + case TimerStatus::completed: + remove(timer); + break; + case TimerStatus::repeat: + if (timer.repeat > 0) { + timer.start = now; + timer.expires = timer.repeat; + } else { + remove(timer); + } + break; + case TimerStatus::reschedule: + timer.start = now; + timer.expires = next; + break; + } + } + } + + // compute lowest remaining time after all handlers have been executed + // (some timers may have expired during handler execution) + Timepoint now = clock::now(); + + for (auto& timer: timers) { + if (!timer.handler) { + continue; + } + + Timepoint remaining = timer.expires - (now - timer.start); + next_expiration = remaining < next_expiration ? remaining : next_expiration; + } + + return next_expiration == std::numeric_limits::max() ? 0 : next_expiration; + } + + // Ticks the timerset forward, then delays until next timer is due + void + tick_and_delay() + { + clock::delay(tick()); + } +}; + + +// create TimerSet with default settings +TimerSet<> +create_default() +{ + return TimerSet<>(); +} + +}; // end namespace Timers From a8ff258523bc955969a5f69f21fcaa5577af257c Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Wed, 26 Aug 2020 06:40:08 -0400 Subject: [PATCH 24/31] add noexcept specifications to all functions improve formatting --- src/arduino-timer-cpp17.hpp | 68 +++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/src/arduino-timer-cpp17.hpp b/src/arduino-timer-cpp17.hpp index b43ad66..f80e0b9 100644 --- a/src/arduino-timer-cpp17.hpp +++ b/src/arduino-timer-cpp17.hpp @@ -61,7 +61,8 @@ enum class TimerStatus reschedule }; -struct HandlerResult { +struct HandlerResult +{ TimerStatus status; Timepoint next; @@ -74,7 +75,8 @@ struct HandlerResult { using Handler = std::function; -struct Timer { +struct Timer +{ Handler handler; Timepoint start; // when timer was added (or repeat execution began) Timepoint expires; // when the timer expires @@ -89,31 +91,35 @@ struct Timer { using TimerHandle = std::optional>; -struct Clock { - struct millis { +struct Clock +{ + struct millis + { static - Timepoint - now() { + Timepoint now() noexcept + { return ::millis(); } static void - delay(Timepoint until) { + delay(Timepoint until) noexcept + { ::delay(until); } }; - struct micros { + struct micros + { static - Timepoint - now() { + Timepoint now() noexcept + { return ::micros(); } static - void - delay(Timepoint until) { + void delay(Timepoint until) noexcept + { unsigned int micros = until % 1000; until -= micros; until /= 1000; @@ -126,10 +132,11 @@ struct Clock { template < Timers::Timepoint (*clock_func)() > - struct custom { + struct custom + { static - Timepoint - now() { + Timepoint now() noexcept + { return clock_func(); } }; @@ -139,11 +146,12 @@ template < size_t max_timers = TIMERSET_DEFAULT_TIMERS, // max number of timers typename clock = Clock::millis // clock for timers > -class TimerSet { +class TimerSet +{ std::array timers; void - remove(TimerHandle handle) + remove(TimerHandle handle) noexcept { if (!handle) { return; @@ -158,13 +166,13 @@ class TimerSet { } auto - next_timer_slot() + next_timer_slot() noexcept { return std::find_if(timers.begin(), timers.end(), [](Timer& t){ return !t.handler; }); } TimerHandle - add_timer(Timepoint start, Timepoint expires, Handler h, Timepoint repeat = 0) + add_timer(Timepoint start, Timepoint expires, Handler h, Timepoint repeat = 0) noexcept { if (auto it = next_timer_slot(); it != timers.end()) { it->handler = h; @@ -181,7 +189,7 @@ class TimerSet { // Reschedules handler to be called in delay units of time TimerHandle - reschedule_timer(TimerHandle handle, Timepoint start, Timepoint expires) + reschedule_timer(TimerHandle handle, Timepoint start, Timepoint expires) noexcept { if (!handle) { return handle; @@ -202,14 +210,14 @@ class TimerSet { public: // Calls handler in delay units of time TimerHandle - in(Timepoint delay, Handler h) + in(Timepoint delay, Handler h) noexcept { return add_timer(clock::now(), delay, h); } // Calls handler at time TimerHandle - at(Timepoint when, Handler h) + at(Timepoint when, Handler h) noexcept { Timepoint now = clock::now(); return add_timer(now, when - now, h); @@ -217,14 +225,14 @@ class TimerSet { // Calls handler every interval units of time TimerHandle - every(Timepoint interval, Handler h) + every(Timepoint interval, Handler h) noexcept { return add_timer(clock::now(), interval, h, interval); } // Calls handler immediately and every interval units of time TimerHandle - now_and_every(Timepoint interval, Handler h) + now_and_every(Timepoint interval, Handler h) noexcept { Timepoint now = clock::now(); return add_timer(now, now, h, interval); @@ -232,7 +240,7 @@ class TimerSet { // Cancels timer TimerHandle - cancel(TimerHandle handle) + cancel(TimerHandle handle) noexcept { if (!handle) { return handle; @@ -251,14 +259,14 @@ class TimerSet { // Reschedules handler to be called in delay units of time TimerHandle - reschedule_in(TimerHandle handle, Timepoint delay) + reschedule_in(TimerHandle handle, Timepoint delay) noexcept { return reschedule_timer(handle, clock::now(), delay); } // Reschedules handler to be called at time TimerHandle - reschedule_at(TimerHandle handle, Timepoint when) + reschedule_at(TimerHandle handle, Timepoint when) noexcept { Timepoint now = clock::now(); return reschedule_timer(handle, now, when - now); @@ -267,7 +275,7 @@ class TimerSet { // Ticks the timerset forward - call this function in loop() // returns Timepoint of next timer expiration */ Timepoint - tick() + tick() noexcept { Timepoint next_expiration = std::numeric_limits::max(); @@ -321,7 +329,7 @@ class TimerSet { // Ticks the timerset forward, then delays until next timer is due void - tick_and_delay() + tick_and_delay() noexcept { clock::delay(tick()); } @@ -330,7 +338,7 @@ class TimerSet { // create TimerSet with default settings TimerSet<> -create_default() +create_default() noexcept { return TimerSet<>(); } From 8a7a9c1fde2bc9489fbac4597006b783230bbecc Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Fri, 9 Oct 2020 17:49:03 -0400 Subject: [PATCH 25/31] add 'operator bool()' to Timer, to avoid peeking inside it --- library.properties | 2 +- src/arduino-timer-cpp17.hpp | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/library.properties b/library.properties index 3d1c3df..ff1b16c 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=arduino-timer-cpp17 -version=3.0.0 +version=4.2.0 author=Kevin P. Fleming maintainer=Kevin P. Fleming diff --git a/src/arduino-timer-cpp17.hpp b/src/arduino-timer-cpp17.hpp index f80e0b9..29d3127 100644 --- a/src/arduino-timer-cpp17.hpp +++ b/src/arduino-timer-cpp17.hpp @@ -87,6 +87,12 @@ struct Timer Timer() = default; Timer(const Timer&) = delete; Timer& operator=(const Timer&) = delete; + + // boolean to indicate whether this timer is active + explicit operator bool() const noexcept + { + return static_cast(handler); + } }; using TimerHandle = std::optional>; @@ -166,9 +172,9 @@ class TimerSet } auto - next_timer_slot() noexcept + next_timer_slot() const noexcept { - return std::find_if(timers.begin(), timers.end(), [](Timer& t){ return !t.handler; }); + return std::find_if(timers.begin(), timers.end(), [](Timer& t){ return !t; }); } TimerHandle @@ -197,7 +203,7 @@ class TimerSet auto& timer = handle.value().get(); - if (!timer.handler) { + if (!timer) { return handle; } @@ -248,7 +254,7 @@ class TimerSet auto& timer = handle.value().get(); - if (!timer.handler) { + if (!timer) { return handle; } @@ -281,7 +287,7 @@ class TimerSet // execute handlers for any timers which have expired for (auto& timer: timers) { - if (!timer.handler) { + if (!timer) { continue; } @@ -316,7 +322,7 @@ class TimerSet Timepoint now = clock::now(); for (auto& timer: timers) { - if (!timer.handler) { + if (!timer) { continue; } From 7653b6c3b232808c44aae3f6e169b992c97c7905 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Sat, 10 Oct 2020 07:53:52 -0400 Subject: [PATCH 26/31] remove 'const' which causes const iterator to be returned --- library.properties | 2 +- src/arduino-timer-cpp17.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library.properties b/library.properties index ff1b16c..debf165 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=arduino-timer-cpp17 -version=4.2.0 +version=4.2.1 author=Kevin P. Fleming maintainer=Kevin P. Fleming diff --git a/src/arduino-timer-cpp17.hpp b/src/arduino-timer-cpp17.hpp index 29d3127..85eab17 100644 --- a/src/arduino-timer-cpp17.hpp +++ b/src/arduino-timer-cpp17.hpp @@ -172,7 +172,7 @@ class TimerSet } auto - next_timer_slot() const noexcept + next_timer_slot() noexcept { return std::find_if(timers.begin(), timers.end(), [](Timer& t){ return !t; }); } From 703c336c3a5c80db7e84e89b623ff4b4d7e145b9 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Mon, 19 Oct 2020 06:56:39 -0400 Subject: [PATCH 27/31] move create_default() into a proper translation unit --- .gitignore | 1 + library.properties | 2 +- src/arduino-timer-cpp17.cpp | 47 +++++++++++++++++++++++++++++++++++++ src/arduino-timer-cpp17.hpp | 8 ------- 4 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 src/arduino-timer-cpp17.cpp diff --git a/.gitignore b/.gitignore index e69de29..c4c4ffc 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1 @@ +*.zip diff --git a/library.properties b/library.properties index debf165..71bd1b4 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=arduino-timer-cpp17 -version=4.2.1 +version=4.3.0 author=Kevin P. Fleming maintainer=Kevin P. Fleming diff --git a/src/arduino-timer-cpp17.cpp b/src/arduino-timer-cpp17.cpp new file mode 100644 index 0000000..a5c9cf3 --- /dev/null +++ b/src/arduino-timer-cpp17.cpp @@ -0,0 +1,47 @@ +/** + arduino-timer - library for delaying function calls + + Copyright (c) 2018, Michael Contreras + Copyright (c) 2020, Kevin P. Fleming + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "arduino-timer-cpp17.hpp" + +namespace Timers { + +// create TimerSet with default settings +TimerSet<> +create_default() noexcept +{ + return TimerSet<>(); +} + +}; // end namespace Timers diff --git a/src/arduino-timer-cpp17.hpp b/src/arduino-timer-cpp17.hpp index 85eab17..d97ec88 100644 --- a/src/arduino-timer-cpp17.hpp +++ b/src/arduino-timer-cpp17.hpp @@ -341,12 +341,4 @@ class TimerSet } }; - -// create TimerSet with default settings -TimerSet<> -create_default() noexcept -{ - return TimerSet<>(); -} - }; // end namespace Timers From a68b04d8ea7342773b118d48c381774d273089f8 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Mon, 19 Oct 2020 07:00:37 -0400 Subject: [PATCH 28/31] Simplify directory name in generated ZIP files --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 57ab510..10c9af1 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ NAME := arduino-timer-cpp17 VERSION := $(shell git describe --tags --always --dirty) $(NAME)-$(VERSION).zip: - git archive HEAD --prefix=$(@:.zip=)/ --format=zip -o $@ + git archive HEAD --prefix=$(NAME)/ --format=zip -o $@ tag: git tag $(VERSION) From 1778bb21d723a1883fc557a91ca5135fd8cd2781 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Thu, 22 Oct 2020 08:12:12 -0400 Subject: [PATCH 29/31] Use rvalue references and std::move to avoid copying Handler objects --- src/arduino-timer-cpp17.hpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/arduino-timer-cpp17.hpp b/src/arduino-timer-cpp17.hpp index d97ec88..edce3b8 100644 --- a/src/arduino-timer-cpp17.hpp +++ b/src/arduino-timer-cpp17.hpp @@ -178,10 +178,10 @@ class TimerSet } TimerHandle - add_timer(Timepoint start, Timepoint expires, Handler h, Timepoint repeat = 0) noexcept + add_timer(Timepoint start, Timepoint expires, Handler&& h, Timepoint repeat = 0) noexcept { if (auto it = next_timer_slot(); it != timers.end()) { - it->handler = h; + it->handler = std::move(h); it->start = start; it->expires = expires; it->repeat = repeat; @@ -216,32 +216,32 @@ class TimerSet public: // Calls handler in delay units of time TimerHandle - in(Timepoint delay, Handler h) noexcept + in(Timepoint delay, Handler&& h) noexcept { - return add_timer(clock::now(), delay, h); + return add_timer(clock::now(), delay, std::move(h)); } // Calls handler at time TimerHandle - at(Timepoint when, Handler h) noexcept + at(Timepoint when, Handler&& h) noexcept { Timepoint now = clock::now(); - return add_timer(now, when - now, h); + return add_timer(now, when - now, std::move(h)); } // Calls handler every interval units of time TimerHandle - every(Timepoint interval, Handler h) noexcept + every(Timepoint interval, Handler&& h) noexcept { - return add_timer(clock::now(), interval, h, interval); + return add_timer(clock::now(), interval, std::move(h), interval); } // Calls handler immediately and every interval units of time TimerHandle - now_and_every(Timepoint interval, Handler h) noexcept + now_and_every(Timepoint interval, Handler&& h) noexcept { Timepoint now = clock::now(); - return add_timer(now, now, h, interval); + return add_timer(now, now, std::move(h), interval); } // Cancels timer From 3f9085ef965c68533b454086ef94d2a349d6998e Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Fri, 23 Oct 2020 06:34:36 -0400 Subject: [PATCH 30/31] Revert "Simplify directory name in generated ZIP files" This reverts commit a68b04d8ea7342773b118d48c381774d273089f8. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 10c9af1..57ab510 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ NAME := arduino-timer-cpp17 VERSION := $(shell git describe --tags --always --dirty) $(NAME)-$(VERSION).zip: - git archive HEAD --prefix=$(NAME)/ --format=zip -o $@ + git archive HEAD --prefix=$(@:.zip=)/ --format=zip -o $@ tag: git tag $(VERSION) From d3fae08ec1ecb17752c105abfd8b3abbb1b0f651 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Mon, 26 Oct 2020 20:38:36 -0400 Subject: [PATCH 31/31] disable move operations on Timer update library.properties --- library.properties | 2 +- src/arduino-timer-cpp17.hpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/library.properties b/library.properties index 71bd1b4..837438b 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=arduino-timer-cpp17 -version=4.3.0 +version=4.5.0 author=Kevin P. Fleming maintainer=Kevin P. Fleming diff --git a/src/arduino-timer-cpp17.hpp b/src/arduino-timer-cpp17.hpp index edce3b8..caf1007 100644 --- a/src/arduino-timer-cpp17.hpp +++ b/src/arduino-timer-cpp17.hpp @@ -87,6 +87,8 @@ struct Timer Timer() = default; Timer(const Timer&) = delete; Timer& operator=(const Timer&) = delete; + Timer(Timer&&) = delete; + Timer& operator=(Timer&&) = delete; // boolean to indicate whether this timer is active explicit operator bool() const noexcept