diff options
| -rw-r--r--[-rwxr-xr-x] | src/TaskScheduler.h | 1209 | ||||
| -rw-r--r-- | src/TaskSchedulerDeclarations.h | 309 |
2 files changed, 1011 insertions, 507 deletions
diff --git a/src/TaskScheduler.h b/src/TaskScheduler.h index 89ae8a2..9afeac9 100755..100644 --- a/src/TaskScheduler.h +++ b/src/TaskScheduler.h @@ -1,49 +1,53 @@ -// Cooperative multitasking library for Arduino version 2.0.2sd -// Copyright (c) 2015 Anatoli Arkhipenko +// Cooperative multitasking library for Arduino +// Copyright (c) 2015-2017 Anatoli Arkhipenko // // Changelog: // v1.0.0: -// 2015-02-24 - Initial release +// 2015-02-24 - Initial release // 2015-02-28 - added delay() and disableOnLastIteration() methods // 2015-03-25 - changed scheduler execute() method for a more precise delay calculation: // 1. Do not delay if any of the tasks ran (making request for immediate execution redundant) -// 2. Delay is invoked only if none of the tasks ran +// 2. Delay is invoked only if none of the tasks ran // 3. Delay is based on the min anticipated wait until next task _AND_ the runtime of execute method itself. // 2015-05-11 - added restart() and restartDelayed() methods to restart tasks which are on hold after running all iterations // 2015-05-19 - completely removed delay from the scheduler since there are no power saving there. using 1 ms sleep instead // // v1.4.1: // 2015-09-15 - more careful placement of AVR-specific includes for sleep method (compatibility with DUE) -// sleep on idle run is no longer a default and should be explicitly compiled with _TASK_SLEEP_ON_IDLE_RUN defined +// sleep on idle run is no longer a default and should be explicitly compiled with +// _TASK_SLEEP_ON_IDLE_RUN defined // // v1.5.0: -// 2015-09-20 - access to currently executing task (for callback methods) -// 2015-09-20 - pass scheduler as a parameter to the task constructor to append the task to the end of the chain +// 2015-09-20 - access to currently executing task (for callback methods) +// 2015-09-20 - pass scheduler as a parameter to the task constructor to append the task to the end of the chain // 2015-09-20 - option to create a task already enabled // // v1.5.1: -// 2015-09-21 - bug fix: incorrect handling of active tasks via set() and setIterations(). -// Thanks to Hannes Morgenstern for catching this one +// 2015-09-21 - bug fix: incorrect handling of active tasks via set() and setIterations(). +// Thanks to Hannes Morgenstern for catching this one // // v1.6.0: -// 2015-09-22 - revert back to having all tasks disable on last iteration. -// 2015-09-22 - deprecated disableOnLastIteration method as a result -// 2015-09-22 - created a separate branch 'disable-on-last-iteration' for this -// 2015-10-01 - made version numbers semver compliant (documentation only) +// 2015-09-22 - revert back to having all tasks disable on last iteration. +// 2015-09-22 - deprecated disableOnLastIteration method as a result +// 2015-09-22 - created a separate branch 'disable-on-last-iteration' for this +// 2015-10-01 - made version numbers semver compliant (documentation only) // // v1.7.0: -// 2015-10-08 - introduced callback run counter - callback methods can branch on the iteration number. -// 2015-10-11 - enableIfNot() - enable a task only if it is not already enabled. Returns true if was already enabled, false if was disabled. -// 2015-10-11 - disable() returns previous enable state (true if was enabled, false if was already disabled) -// 2015-10-11 - introduced callback methods "on enable" and "on disable". On enable runs every time enable is called, on disable runs only if task was enabled -// 2015-10-12 - new Task method: forceNextIteration() - makes next iteration happen immediately during the next pass regardless how much time is left +// 2015-10-08 - introduced callback run counter - callback methods can branch on the iteration number. +// 2015-10-11 - enableIfNot() - enable a task only if it is not already enabled. Returns true if was already enabled, +// false if was disabled. +// 2015-10-11 - disable() returns previous enable state (true if was enabled, false if was already disabled) +// 2015-10-11 - introduced callback methods "on enable" and "on disable". On enable runs every time enable is called, +// on disable runs only if task was enabled +// 2015-10-12 - new Task method: forceNextIteration() - makes next iteration happen immediately during the next pass +// regardless how much time is left // // v1.8.0: -// 2015-10-13 - support for status request objects allowing tasks waiting on requests -// 2015-10-13 - moved to a single header file to allow compilation control via #defines from the main sketch +// 2015-10-13 - support for status request objects allowing tasks waiting on requests +// 2015-10-13 - moved to a single header file to allow compilation control via #defines from the main sketch // // v1.8.1: -// 2015-10-22 - implement Task id and control points to support identification of failure points for watchdog timer logging +// 2015-10-22 - implement Task id and control points to support identification of failure points for watchdog timer logging // // v1.8.2: // 2015-10-27 - implement Local Task Storage Pointer (allow use of same callback code for different tasks) @@ -52,14 +56,17 @@ // 2015-10-29 - new currentLts() method in the scheduler class returns current task's LTS pointer in one call // // v1.8.3: -// 2015-11-05 - support for task activation on a status request with arbitrary interval and number of iterations (0 and 1 are still default values) -// 2015-11-05 - implement waitForDelayed() method to allow task activation on the status request completion delayed for one current interval +// 2015-11-05 - support for task activation on a status request with arbitrary interval and number of iterations +// (0 and 1 are still default values) +// 2015-11-05 - implement waitForDelayed() method to allow task activation on the status request completion +// delayed for one current interval // 2015-11-09 - added callback methods prototypes to all examples for Arduino IDE 1.6.6 compatibility // 2015-11-14 - added several constants to be used as task parameters for readability (e.g, TASK_FOREVER, TASK_SECOND, etc.) // 2015-11-14 - significant optimization of the scheduler's execute loop, including millis() rollover fix option // // v1.8.4: -// 2015-11-15 - bug fix: Task alignment with millis() for scheduling purposes should be done after OnEnable, not before. Especially since OnEnable method can change the interval +// 2015-11-15 - bug fix: Task alignment with millis() for scheduling purposes should be done after OnEnable, not before. +// Especially since OnEnable method can change the interval // 2015-11-16 - further optimizations of the task scheduler execute loop // // v1.8.5: @@ -72,7 +79,7 @@ // v1.9.2: // 2015-11-28 - _TASK_ROLLOVER_FIX is deprecated (not necessary) // 2015-12-16 - bug fixes: automatic millis rollover support for delay methods -// 2015-12-17 - new method for _TASK_TIMECRITICAL option: getStartDelay() +// 2015-12-17 - new method for _TASK_TIMECRITICAL option: getStartDelay() // // v2.0.0: // 2015-12-22 - _TASK_PRIORITY - support for layered task prioritization @@ -87,360 +94,316 @@ // v2.1.0: // 2016-02-01 - support for microsecond resolution // 2016-02-02 - added Scheduler baseline start time reset method: startNow() - -/* ============================================ -Cooperative multitasking library code is placed under the MIT license -Copyright (c) 2015 Anatoli Arkhipenko - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -=============================================== -*/ - +// +// v2.2.0: +// 2016-11-17 - all methods made 'inline' to support inclusion of TaskSchedule.h file into other header files +// +// v2.2.1: +// 2016-11-30 - inlined constructors. Added "yield()" and "yieldOnce()" functions to easily break down and chain +// back together long running callback methods +// 2016-12-16 - added "getCount()" to StatusRequest objects, made every task StatusRequest enabled. +// Internal StatusRequest objects are accessible via "getInternalStatusRequest()" method. +// +// v2.3.0: +// 2017-02-24 - new timeUntilNextIteration() method within Scheduler class - inquire when a particlar task is +// scheduled to run next time +// +// v2.4.0: +// 2017-04-27 - added destructor to the Task class to ensure tasks are disables and taken off the execution chain +// upon destruction. (Contributed by Edwin van Leeuwen [BlackEdder - https://github.com/BlackEdder) +// +// v2.5.0: +// 2017-04-27 - ESP8266 ONLY: added optional support for std::functions via _TASK_STD_FUNCTION compilation option +// (Contributed by Edwin van Leeuwen [BlackEdder - https://github.com/BlackEdder) +// 2017-08-30 - add _TASK_DEBUG making all methods and variables public FOR DEBUGGING PURPOSES ONLY! +// Use at your own risk! +// 2017-08-30 - bug fix: Scheduler::addTask() checks if task is already part of an execution chain (github issue #37) +// 2017-08-30 - support for multi-tab sketches (Contributed by Adam Ryczkowski - https://github.com/adamryczkowski) +// +// v2.5.1: +// 2018-01-06 - support for IDLE sleep on Teensy boards (tested on Teensy 3.5) +// +// v2.5.2: +// 2018-01-09 - _TASK_INLINE compilation directive making all methods declared "inline" (issue #42) +// +// v2.6.0: +// 2018-01-30 - _TASK_TIMEOUT compilation directive: Task overall timeout functionality +// 2018-01-30 - ESP32 support (experimental) +// (Contributed by Marco Tombesi: https://github.com/baggior) +// +// v2.6.1: +// 2018-02-13 - Bug: support for task self-destruction in the OnDisable method +// Example 19: dynamic tasks creation and destruction +// 2018-03-14 - Bug: high level scheduler ignored if lower level chain is empty +// Example 20: use of local task storage to work with task-specific class objects +// +// v3.0.0: +// 2018-03-15 - Major Release: Support for dynamic callback methods binding via compilation parameter _TASK_OO_CALLBACKS +// +// v3.0.1: +// 2018-11-09 - bug: task deleted from the execution chain cannot be added back (github issue #67) +// +// v3.0.2: +// 2018-11-11 - bug: default constructor is ambiguous when Status Request objects are enabled (github issue #65 & #68) #include <Arduino.h> +#include "TaskSchedulerDeclarations.h" #ifndef _TASKSCHEDULER_H_ #define _TASKSCHEDULER_H_ -/** ---------------------------------------- - * The following "defines" control library functionality at compile time, - * and should be used in the main sketch depending on the functionality required - * - * #define _TASK_TIMECRITICAL // Enable monitoring scheduling overruns - * #define _TASK_SLEEP_ON_IDLE_RUN // Enable 1 ms SLEEP_IDLE powerdowns between tasks if no callback methods were invoked during the pass - * #define _TASK_STATUS_REQUEST // Compile with support for StatusRequest functionality - triggering tasks on status change events in addition to time only - * #define _TASK_WDT_IDS // Compile with support for wdt control points and task ids - * #define _TASK_LTS_POINTER // Compile with support for local task storage pointer - * #define _TASK_PRIORITY // Support for layered scheduling priority - * #define _TASK_MICRO_RES // Support for microsecond resolution - */ - +// ---------------------------------------- +// The following "defines" control library functionality at compile time, +// and should be used in the main sketch depending on the functionality required +// +// #define _TASK_TIMECRITICAL // Enable monitoring scheduling overruns +// #define _TASK_SLEEP_ON_IDLE_RUN // Enable 1 ms SLEEP_IDLE powerdowns between tasks if no callback methods were invoked during the pass +// #define _TASK_STATUS_REQUEST // Compile with support for StatusRequest functionality - triggering tasks on status change events in addition to time only +// #define _TASK_WDT_IDS // Compile with support for wdt control points and task ids +// #define _TASK_LTS_POINTER // Compile with support for local task storage pointer +// #define _TASK_PRIORITY // Support for layered scheduling priority +// #define _TASK_MICRO_RES // Support for microsecond resolution +// #define _TASK_STD_FUNCTION // Support for std::function (ESP8266 ONLY) +// #define _TASK_DEBUG // Make all methods and variables public for debug purposes +// #define _TASK_INLINE // Make all methods "inline" - needed to support some multi-tab, multi-file implementations +// #define _TASK_TIMEOUT // Support for overall task timeout +// #define _TASK_OO_CALLBACKS // Support for callbacks via inheritance #ifdef _TASK_MICRO_RES - - #undef _TASK_SLEEP_ON_IDLE_RUN // SLEEP_ON_IDLE has only millisecond resolution + + #undef _TASK_SLEEP_ON_IDLE_RUN // SLEEP_ON_IDLE has only millisecond resolution #define _TASK_TIME_FUNCTION() micros() - + #else - #define _TASK_TIME_FUNCTION() millis() - + #endif // _TASK_MICRO_RES - - + + #ifdef _TASK_SLEEP_ON_IDLE_RUN -#ifdef ARDUINO_ARCH_AVR +#ifdef ARDUINO_ARCH_AVR #include <avr/sleep.h> #include <avr/power.h> -#endif // ARDUINO_ARCH_AVR +#endif // ARDUINO_ARCH_AVR #ifdef ARDUINO_ARCH_ESP8266 +#define _TASK_ESP8266_DLY_THRESHOLD 200L extern "C" { #include "user_interface.h" } -#define _TASK_ESP8266_DLY_THRESHOLD 200L -#endif // ARDUINO_ARCH_ESP8266 - -#endif // _TASK_SLEEP_ON_IDLE_RUN +#endif //ARDUINO_ARCH_ESP8266 -#define TASK_IMMEDIATE 0 -#define TASK_FOREVER (-1) -#define TASK_ONCE 1 +#ifdef ARDUINO_ARCH_ESP32 +#define _TASK_ESP8266_DLY_THRESHOLD 200L +#warning _TASK_SLEEP_ON_IDLE_RUN for ESP32 cannot use light sleep mode but a standard delay for 1 ms +#endif // ARDUINO_ARCH_ESP32 +#ifdef ARDUINO_ARCH_STM32F1 +#include <libmaple/pwr.h> +#include <libmaple/scb.h> +#endif // ARDUINO_ARCH_STM32F1 -#ifndef _TASK_MICRO_RES - -#define TASK_SECOND 1000L -#define TASK_MINUTE 60000L -#define TASK_HOUR 3600000L +#endif // _TASK_SLEEP_ON_IDLE_RUN -#else -#define TASK_SECOND 1000000L -#define TASK_MINUTE 60000000L -#define TASK_HOUR 3600000000L +#if !defined (ARDUINO_ARCH_ESP8266) && !defined (ARDUINO_ARCH_ESP32) +#ifdef _TASK_STD_FUNCTION + #error Support for std::function only for ESP8266 or ESP32 architecture +#undef _TASK_STD_FUNCTION +#endif // _TASK_STD_FUNCTION +#endif // ARDUINO_ARCH_ESP8266 -#endif // _TASK_MICRO_RES +#ifdef _TASK_WDT_IDS + static unsigned int __task_id_counter = 0; // global task ID counter for assiging task IDs automatically. +#endif // _TASK_WDT_IDS +#ifdef _TASK_PRIORITY + Scheduler* iCurrentScheduler; +#endif // _TASK_PRIORITY -#ifdef _TASK_STATUS_REQUEST -#define _TASK_SR_NODELAY 1 -#define _TASK_SR_DELAY 2 - -class StatusRequest { - public: - StatusRequest() {iCount = 0; iStatus = 0; } - inline void setWaiting(unsigned int aCount = 1) { iCount = aCount; iStatus = 0; } - bool signal(int aStatus = 0); - void signalComplete(int aStatus = 0); - inline bool pending() { return (iCount != 0); } - inline bool completed() { return (iCount == 0); } - inline int getStatus() { return iStatus; } - - private: - unsigned int iCount; // number of statuses to wait for. waiting for more that 65000 events seems unreasonable: unsigned int should be sufficient - int iStatus; // status of the last completed request. negative = error; zero = OK; >positive = OK with a specific status -}; -#endif // _TASK_STATUS_REQUEST +// ------------------ TaskScheduler implementation -------------------- -typedef struct { - bool enabled : 1; // indicates that task is enabled or not. - bool inonenable : 1; // indicates that task execution is inside OnEnable method (preventing infinite loops) -#ifdef _TASK_STATUS_REQUEST - byte waiting : 2; // indication if task is waiting on the status request +/** Constructor, uses default values for the parameters + * so could be called with no parameters. + */ +#ifdef _TASK_OO_CALLBACKS +Task::Task( unsigned long aInterval, long aIterations, Scheduler* aScheduler, bool aEnable ) { + reset(); + set(aInterval, aIterations); +#else +Task::Task( unsigned long aInterval, long aIterations, TaskCallback aCallback, Scheduler* aScheduler, bool aEnable, TaskOnEnable aOnEnable, TaskOnDisable aOnDisable ) { + reset(); + set(aInterval, aIterations, aCallback, aOnEnable, aOnDisable); #endif -} __task_status; - -class Scheduler; + if (aScheduler) aScheduler->addTask(*this); #ifdef _TASK_WDT_IDS - static unsigned int __task_id_counter = 0; // global task ID counter for assiging task IDs automatically. + iTaskID = ++__task_id_counter; #endif // _TASK_WDT_IDS -class Task { - friend class Scheduler; - public: - Task(unsigned long aInterval=0, long aIterations=0, void (*aCallback)()=NULL, Scheduler* aScheduler=NULL, bool aEnable=false, bool (*aOnEnable)()=NULL, void (*aOnDisable)()=NULL); -#ifdef _TASK_STATUS_REQUEST - Task(void (*aCallback)()=NULL, Scheduler* aScheduler=NULL, bool (*aOnEnable)()=NULL, void (*aOnDisable)()=NULL); -#endif // _TASK_STATUS_REQUEST + if (aEnable) enable(); +} - void enable(); - bool enableIfNot(); - void enableDelayed(unsigned long aDelay=0); - void delay(unsigned long aDelay=0); - void forceNextIteration(); - void restart(); - void restartDelayed(unsigned long aDelay=0); - bool disable(); - inline bool isEnabled() { return iStatus.enabled; } - void set(unsigned long aInterval, long aIterations, void (*aCallback)(),bool (*aOnEnable)()=NULL, void (*aOnDisable)()=NULL); - void setInterval(unsigned long aInterval); - inline unsigned long getInterval() { return iInterval; } - void setIterations(long aIterations); - inline long getIterations() { return iIterations; } - inline unsigned long getRunCounter() { return iRunCounter; } - inline void setCallback(void (*aCallback)()) { iCallback = aCallback; } - inline void setOnEnable(bool (*aCallback)()) { iOnEnable = aCallback; } - inline void setOnDisable(void (*aCallback)()) { iOnDisable = aCallback; } -#ifdef _TASK_TIMECRITICAL - inline long getOverrun() { return iOverrun; } - inline long getStartDelay() { return iStartDelay; } -#endif // _TASK_TIMECRITICAL - inline bool isFirstIteration() { return (iRunCounter <= 1); } - inline bool isLastIteration() { return (iIterations == 0); } -#ifdef _TASK_STATUS_REQUEST - void waitFor(StatusRequest* aStatusRequest, unsigned long aInterval = 0, long aIterations = 1); - void waitForDelayed(StatusRequest* aStatusRequest, unsigned long aInterval = 0, long aIterations = 1); - inline StatusRequest* getStatusRequest() {return iStatusRequest; } -#endif // _TASK_STATUS_REQUEST -#ifdef _TASK_WDT_IDS - inline void setId(unsigned int aID) { iTaskID = aID; } - inline unsigned int getId() { return iTaskID; } - inline void setControlPoint(unsigned int aPoint) { iControlPoint = aPoint; } - inline unsigned int getControlPoint() { return iControlPoint; } -#endif // _TASK_WDT_IDS -#ifdef _TASK_LTS_POINTER - inline void setLtsPointer(void *aPtr) { iLTS = aPtr; } - inline void* getLtsPointer() { return iLTS; } -#endif // _TASK_LTS_POINTER - - private: - void reset(); - - volatile __task_status iStatus; - volatile unsigned long iInterval; // execution interval in milliseconds (or microseconds). 0 - immediate - volatile unsigned long iDelay; // actual delay until next execution (usually equal iInterval) - volatile unsigned long iPreviousMillis; // previous invocation time (millis). Next invocation = iPreviousMillis + iInterval. Delayed tasks will "catch up" -#ifdef _TASK_TIMECRITICAL - volatile long iOverrun; // negative if task is "catching up" to it's schedule (next invocation time is already in the past) - volatile long iStartDelay; // actual execution of the task's callback method was delayed by this number of millis -#endif // _TASK_TIMECRITICAL - volatile long iIterations; // number of iterations left. 0 - last iteration. -1 - infinite iterations - long iSetIterations; // number of iterations originally requested (for restarts) - unsigned long iRunCounter; // current number of iteration (starting with 1). Resets on enable. - void (*iCallback)(); // pointer to the void callback method - bool (*iOnEnable)(); // pointer to the bolol OnEnable callback method - void (*iOnDisable)(); // pointer to the void OnDisable method - Task *iPrev, *iNext; // pointers to the previous and next tasks in the chain - Scheduler *iScheduler; // pointer to the current scheduler -#ifdef _TASK_STATUS_REQUEST - StatusRequest *iStatusRequest; // pointer to the status request task is or was waiting on -#endif // _TASK_STATUS_REQUEST -#ifdef _TASK_WDT_IDS - unsigned int iTaskID; // task ID (for debugging and watchdog identification) - unsigned int iControlPoint; // current control point within the callback method. Reset to 0 by scheduler at the beginning of each pass -#endif // _TASK_WDT_IDS -#ifdef _TASK_LTS_POINTER - void *iLTS; // pointer to task's local storage. Needs to be recast to appropriate type (usually a struct). -#endif // _TASK_LTS_POINTER -}; +/** Destructor. + * Makes sure the task disabled and deleted out of the chain + * prior to being deleted. + */ +Task::~Task() { + disable(); + if (iScheduler) + iScheduler->deleteTask(*this); +} -#ifdef _TASK_PRIORITY - static Scheduler* iCurrentScheduler; -#endif // _TASK_PRIORITY +#ifdef _TASK_STATUS_REQUEST -class Scheduler { - friend class Task; - public: - Scheduler(); - void init(); - void addTask(Task& aTask); - void deleteTask(Task& aTask); - void disableAll(bool aRecursive = true); - void enableAll(bool aRecursive = true); - bool execute(); // Returns true if at none of the tasks' callback methods was invoked (true if idle run) - void startNow(bool aRecursive = true); // reset ALL active tasks to immediate execution NOW. - inline Task& currentTask() {return *iCurrent; } -#ifdef _TASK_SLEEP_ON_IDLE_RUN - void allowSleep(bool aState = true); -#endif // _TASK_SLEEP_ON_IDLE_RUN -#ifdef _TASK_LTS_POINTER - inline void* currentLts() {return iCurrent->iLTS; } -#endif // _TASK_LTS_POINTER -#ifdef _TASK_TIMECRITICAL - inline bool isOverrun() { return (iCurrent->iOverrun < 0); } -#endif // _TASK_TIMECRITICAL -#ifdef _TASK_PRIORITY - void setHighPriorityScheduler(Scheduler* aScheduler); - static Scheduler& currentScheduler() { return *(iCurrentScheduler); }; -#endif // _TASK_PRIORITY +/** Constructor with reduced parameter list for tasks created for + * StatusRequest only triggering (always immediate and only 1 iteration) + */ - private: - Task *iFirst, *iLast, *iCurrent; // pointers to first, last and current tasks in the chain -#ifdef _TASK_SLEEP_ON_IDLE_RUN - bool iAllowSleep; // indication if putting avr to IDLE_SLEEP mode is allowed by the program at this time. -#endif // _TASK_SLEEP_ON_IDLE_RUN -#ifdef _TASK_PRIORITY - Scheduler *iHighPriority; // Pointer to a higher priority scheduler -#endif // _TASK_PRIORITY -}; +#ifdef _TASK_OO_CALLBACKS +Task::Task( Scheduler* aScheduler ) { + reset(); + set(TASK_IMMEDIATE, TASK_ONCE); +#else +Task::Task( TaskCallback aCallback, Scheduler* aScheduler, TaskOnEnable aOnEnable, TaskOnDisable aOnDisable ) { + reset(); + set(TASK_IMMEDIATE, TASK_ONCE, aCallback, aOnEnable, aOnDisable); +#endif // _TASK_OO_CALLBACKS -// ------------------ TaskScheduler implementation -------------------- + if (aScheduler) aScheduler->addTask(*this); -/** Constructor, uses default values for the parameters - * so could be called with no parameters. - */ -inline Task::Task( unsigned long aInterval, long aIterations, void (*aCallback)(), Scheduler* aScheduler, bool aEnable, bool (*aOnEnable)(), void (*aOnDisable)() ) { - reset(); - set(aInterval, aIterations, aCallback, aOnEnable, aOnDisable); - if (aScheduler) aScheduler->addTask(*this); -#ifdef _TASK_STATUS_REQUEST - iStatusRequest = NULL; -#endif // _TASK_STATUS_REQUEST #ifdef _TASK_WDT_IDS - iTaskID = ++__task_id_counter; + iTaskID = ++__task_id_counter; #endif // _TASK_WDT_IDS - if (aEnable) enable(); } -#ifdef _TASK_STATUS_REQUEST - -/** Constructor with reduced parameter list for tasks created for - * StatusRequest only triggering (always immediate and only 1 iteration) - */ -inline Task::Task( void (*aCallback)(), Scheduler* aScheduler, bool (*aOnEnable)(), void (*aOnDisable)() ) { - reset(); - set(TASK_IMMEDIATE, TASK_ONCE, aCallback, aOnEnable, aOnDisable); - if (aScheduler) aScheduler->addTask(*this); - iStatusRequest = NULL; -#ifdef _TASK_WDT_IDS - iTaskID = ++__task_id_counter; -#endif // _TASK_WDT_IDS +StatusRequest::StatusRequest() +{ + iCount = 0; + iStatus = 0; } +void StatusRequest::setWaiting(unsigned int aCount) { iCount = aCount; iStatus = 0; } +bool StatusRequest::pending() { return (iCount != 0); } +bool StatusRequest::completed() { return (iCount == 0); } +int StatusRequest::getStatus() { return iStatus; } +int StatusRequest::getCount() { return iCount; } +StatusRequest* Task::getStatusRequest() { return iStatusRequest; } +StatusRequest* Task::getInternalStatusRequest() { return &iMyStatusRequest; } + /** Signals completion of the StatusRequest by one of the participating events * @param: aStatus - if provided, sets the return code of the StatusRequest: negative = error, 0 (default) = OK, positive = OK with a specific status code * Negative status will complete Status Request fully (since an error occured). * @return: true, if StatusRequest is complete, false otherwise (still waiting for other events) */ -inline bool StatusRequest::signal(int aStatus) { - if ( iCount) { // do not update the status request if it was already completed - if (iCount > 0) --iCount; - if ( (iStatus = aStatus) < 0 ) iCount = 0; // if an error is reported, the status is requested to be completed immediately - } - return (iCount == 0); +bool StatusRequest::signal(int aStatus) { + if ( iCount) { // do not update the status request if it was already completed + if (iCount > 0) --iCount; + if ( (iStatus = aStatus) < 0 ) iCount = 0; // if an error is reported, the status is requested to be completed immediately + } + return (iCount == 0); } -inline void StatusRequest::signalComplete(int aStatus) { - if (iCount) { // do not update the status request if it was already completed - iCount = 0; - iStatus = aStatus; - } +void StatusRequest::signalComplete(int aStatus) { + if (iCount) { // do not update the status request if it was already completed + iCount = 0; + iStatus = aStatus; + } } -/** Sets a Task to wait until a particular event completes +/** Sets a Task to wait until a particular event completes * @param: aStatusRequest - a pointer for the StatusRequest to wait for. - * If aStatusRequest is NULL, request for waiting is ignored, and the waiting task is not enabled. + * If aStatusRequest is NULL, request for waiting is ignored, and the waiting task is not enabled. */ -inline void Task::waitFor(StatusRequest* aStatusRequest, unsigned long aInterval, long aIterations) { - if ( ( iStatusRequest = aStatusRequest) ) { // assign internal StatusRequest var and check if it is not NULL - setIterations(aIterations); - setInterval(aInterval); - iStatus.waiting = _TASK_SR_NODELAY; // no delay - enable(); - } +void Task::waitFor(StatusRequest* aStatusRequest, unsigned long aInterval, long aIterations) { + iStatusRequest = aStatusRequest; + if ( iStatusRequest != NULL ) { // assign internal StatusRequest var and check if it is not NULL + setIterations(aIterations); + setInterval(aInterval); + iStatus.waiting = _TASK_SR_NODELAY; // no delay + enable(); + } } -inline void Task::waitForDelayed(StatusRequest* aStatusRequest, unsigned long aInterval, long aIterations) { - if ( ( iStatusRequest = aStatusRequest) ) { // assign internal StatusRequest var and check if it is not NULL - setIterations(aIterations); - if ( aInterval ) setInterval(aInterval); // For the dealyed version only set the interval if it was not a zero - iStatus.waiting = _TASK_SR_DELAY; // with delay equal to the current interval - enable(); - } +void Task::waitForDelayed(StatusRequest* aStatusRequest, unsigned long aInterval, long aIterations) { + iStatusRequest = aStatusRequest; + if ( iStatusRequest != NULL ) { // assign internal StatusRequest var and check if it is not NULL + setIterations(aIterations); + if ( aInterval ) setInterval(aInterval); // For the dealyed version only set the interval if it was not a zero + iStatus.waiting = _TASK_SR_DELAY; // with delay equal to the current interval + enable(); + } } #endif // _TASK_STATUS_REQUEST +bool Task::isEnabled() { return iStatus.enabled; } + +unsigned long Task::getInterval() { return iInterval; } + +long Task::getIterations() { return iIterations; } + +unsigned long Task::getRunCounter() { return iRunCounter; } + +#ifdef _TASK_OO_CALLBACKS + +// bool Task::Callback() { return true; } +bool Task::OnEnable() { return true; } +void Task::OnDisable() { } + +#else + +void Task::setCallback(TaskCallback aCallback) { iCallback = aCallback; } +void Task::setOnEnable(TaskOnEnable aCallback) { iOnEnable = aCallback; } +void Task::setOnDisable(TaskOnDisable aCallback) { iOnDisable = aCallback; } + +#endif // _TASK_OO_CALLBACKS + + /** Resets (initializes) the task/ - * Task is not enabled and is taken out + * Task is not enabled and is taken out * out of the execution chain as a result */ -inline void Task::reset() { - iStatus.enabled = false; - iStatus.inonenable = false; - iPreviousMillis = 0; - iInterval = iDelay = 0; - iPrev = NULL; - iNext = NULL; - iScheduler = NULL; - iRunCounter = 0; +void Task::reset() { + iStatus.enabled = false; + iStatus.inonenable = false; + iPreviousMillis = 0; + iInterval = iDelay = 0; + iPrev = NULL; + iNext = NULL; + iScheduler = NULL; + iRunCounter = 0; + #ifdef _TASK_TIMECRITICAL - iOverrun = 0; - iStartDelay = 0; + iOverrun = 0; + iStartDelay = 0; #endif // _TASK_TIMECRITICAL + #ifdef _TASK_WDT_IDS - iControlPoint = 0; + iControlPoint = 0; #endif // _TASK_WDT_IDS + #ifdef _TASK_LTS_POINTER - iLTS = NULL; + iLTS = NULL; #endif // _TASK_LTS_POINTER + #ifdef _TASK_STATUS_REQUEST - iStatus.waiting = 0; + iStatusRequest = NULL; + iStatus.waiting = 0; + iMyStatusRequest.signalComplete(); #endif // _TASK_STATUS_REQUEST + +#ifdef _TASK_TIMEOUT + iTimeout = 0; + iStarttime = 0; + iStatus.timeout = false; +#endif // _TASK_TIMEOUT } /** Explicitly set Task execution parameters @@ -450,77 +413,162 @@ inline void Task::reset() { * @param aOnEnable - pointer to the callback method which is called on enable() * @param aOnDisable - pointer to the callback method which is called on disable() */ -inline void Task::set(unsigned long aInterval, long aIterations, void (*aCallback)(),bool (*aOnEnable)(), void (*aOnDisable)()) { - setInterval(aInterval); - iSetIterations = iIterations = aIterations; - iCallback = aCallback; - iOnEnable = aOnEnable; - iOnDisable = aOnDisable; + +#ifdef _TASK_OO_CALLBACKS +void Task::set(unsigned long aInterval, long aIterations) { +#else +void Task::set(unsigned long aInterval, long aIterations, TaskCallback aCallback, TaskOnEnable aOnEnable, TaskOnDisable aOnDisable) { + iCallback = aCallback; + iOnEnable = aOnEnable; + iOnDisable = aOnDisable; +#endif // _TASK_OO_CALLBACKS + + setInterval(aInterval); + iSetIterations = iIterations = aIterations; } /** Sets number of iterations for the task * if task is enabled, schedule for immediate execution * @param aIterations - number of iterations, use -1 for no limit */ -inline void Task::setIterations(long aIterations) { - iSetIterations = iIterations = aIterations; +void Task::setIterations(long aIterations) { + iSetIterations = iIterations = aIterations; +} + +#ifndef _TASK_OO_CALLBACKS + +/** Prepare task for next step iteration following yielding of control to the scheduler + * @param aCallback - pointer to the callback method for the next step + */ +void Task::yield (TaskCallback aCallback) { + iCallback = aCallback; + forceNextIteration(); + + // The next 2 lines adjust runcounter and number of iterations + // as if it is the same run of the callback, just split between + // a series of callback methods + iRunCounter--; + if ( iIterations >= 0 ) iIterations++; } -/** Enables the task +/** Prepare task for next step iteration following yielding of control to the scheduler + * @param aCallback - pointer to the callback method for the next step + */ +void Task::yieldOnce (TaskCallback aCallback) { + yield(aCallback); + iIterations = 1; +} +#endif // _TASK_OO_CALLBACKS + + +/** Enables the task * schedules it for execution as soon as possible, * and resets the RunCounter back to zero */ -inline void Task::enable() { - if (iScheduler) { // activation without active scheduler does not make sense - iRunCounter = 0; - if ( iOnEnable && !iStatus.inonenable ) { - Task *current = iScheduler->iCurrent; - iScheduler->iCurrent = this; - iStatus.inonenable = true; // Protection against potential infinite loop - iStatus.enabled = (*iOnEnable)(); - iStatus.inonenable = false; // Protection against potential infinite loop - iScheduler->iCurrent = current; - } - else { - iStatus.enabled = true; - } - iPreviousMillis = _TASK_TIME_FUNCTION() - (iDelay = iInterval); - } +void Task::enable() { + if (iScheduler) { // activation without active scheduler does not make sense + iRunCounter = 0; + +#ifdef _TASK_OO_CALLBACKS + if ( !iStatus.inonenable ) { + Task *current = iScheduler->iCurrent; + iScheduler->iCurrent = this; + iStatus.inonenable = true; // Protection against potential infinite loop + iStatus.enabled = OnEnable(); + iStatus.inonenable = false; // Protection against potential infinite loop + iScheduler->iCurrent = current; + } +#else + if ( iOnEnable && !iStatus.inonenable ) { + Task *current = iScheduler->iCurrent; + iScheduler->iCurrent = this; + iStatus.inonenable = true; // Protection against potential infinite loop + iStatus.enabled = iOnEnable(); + iStatus.inonenable = false; // Protection against potential infinite loop + iScheduler->iCurrent = current; + } + else { + iStatus.enabled = true; + } +#endif // _TASK_OO_CALLBACKS + + iPreviousMillis = _TASK_TIME_FUNCTION() - (iDelay = iInterval); + +#ifdef _TASK_TIMEOUT + resetTimeout(); +#endif // _TASK_TIMEOUT + +#ifdef _TASK_STATUS_REQUEST + if ( iStatus.enabled ) { + iMyStatusRequest.setWaiting(); + } +#endif // _TASK_STATUS_REQUEST + } } /** Enables the task only if it was not enabled already * Returns previous state (true if was already enabled, false if was not) */ -inline bool Task::enableIfNot() { - bool previousEnabled = iStatus.enabled; - if ( !previousEnabled ) enable(); - return (previousEnabled); +bool Task::enableIfNot() { + bool previousEnabled = iStatus.enabled; + if ( !previousEnabled ) enable(); + return (previousEnabled); } -/** Enables the task +/** Enables the task * and schedules it for execution after a delay = aInterval */ -inline void Task::enableDelayed(unsigned long aDelay) { - enable(); - delay(aDelay); +void Task::enableDelayed(unsigned long aDelay) { + enable(); + delay(aDelay); } +#ifdef _TASK_TIMEOUT +void Task::setTimeout(unsigned long aTimeout, bool aReset) { + iTimeout = aTimeout; + if (aReset) resetTimeout(); +} + +void Task::resetTimeout() { + iStarttime = _TASK_TIME_FUNCTION(); + iStatus.timeout = false; +} + +unsigned long Task::getTimeout() { + return iTimeout; +} + +long Task::untilTimeout() { + if ( iTimeout ) { + return ( (long) (iStarttime + iTimeout) - (long) _TASK_TIME_FUNCTION() ); + } + return -1; +} + +bool Task::timedOut() { + return iStatus.timeout; +} + +#endif // _TASK_TIMEOUT + + + /** Delays Task for execution after a delay = aInterval (if task is enabled). * leaves task enabled or disabled * if aDelay is zero, delays for the original scheduling interval from now */ -inline void Task::delay(unsigned long aDelay) { -// if (!aDelay) aDelay = iInterval; - iDelay = aDelay ? aDelay : iInterval; - iPreviousMillis = _TASK_TIME_FUNCTION(); // - iInterval + aDelay; +void Task::delay(unsigned long aDelay) { +// if (!aDelay) aDelay = iInterval; + iDelay = aDelay ? aDelay : iInterval; + iPreviousMillis = _TASK_TIME_FUNCTION(); // - iInterval + aDelay; } /** Schedules next iteration of Task for execution immediately (if enabled) * leaves task enabled or disabled * Task's original schedule is shifted, and all subsequent iterations will continue from this point in time */ -inline void Task::forceNextIteration() { - iPreviousMillis = _TASK_TIME_FUNCTION() - (iDelay = iInterval); +void Task::forceNextIteration() { + iPreviousMillis = _TASK_TIME_FUNCTION() - (iDelay = iInterval); } /** Sets the execution interval. @@ -528,64 +576,117 @@ inline void Task::forceNextIteration() { * Use enable() to schedule execution ASAP * @param aInterval - new execution interval */ -inline void Task::setInterval (unsigned long aInterval) { - iInterval = aInterval; - delay(); // iDelay will be updated by the delay() function +void Task::setInterval (unsigned long aInterval) { + iInterval = aInterval; + delay(); // iDelay will be updated by the delay() function } /** Disables task * Task will no longer be executed by the scheduler * Returns status of the task before disable was called (i.e., if the task was already disabled) */ -inline bool Task::disable() { - bool previousEnabled = iStatus.enabled; - iStatus.enabled = false; - iStatus.inonenable = false; - if (previousEnabled && iOnDisable) { - Task *current = iScheduler->iCurrent; - iScheduler->iCurrent = this; - (*iOnDisable)(); - iScheduler->iCurrent = current; - } - return (previousEnabled); + +bool Task::disable() { + bool previousEnabled = iStatus.enabled; + iStatus.enabled = false; + iStatus.inonenable = false; + +#ifdef _TASK_OO_CALLBACKS + if (previousEnabled) { +#else + if (previousEnabled && iOnDisable) { +#endif // _TASK_OO_CALLBACKS + + Task *current = iScheduler->iCurrent; + iScheduler->iCurrent = this; +#ifdef _TASK_OO_CALLBACKS + OnDisable(); +#else + iOnDisable(); +#endif // _TASK_OO_CALLBACKS + + iScheduler->iCurrent = current; + } +#ifdef _TASK_STATUS_REQUEST + iMyStatusRequest.signalComplete(); +#endif + return (previousEnabled); } /** Restarts task * Task will run number of iterations again */ -inline void Task::restart() { - iIterations = iSetIterations; - enable(); + +void Task::restart() { + enable(); + iIterations = iSetIterations; } /** Restarts task delayed * Task will run number of iterations again */ -inline void Task::restartDelayed(unsigned long aDelay) { - iIterations = iSetIterations; - enableDelayed(aDelay); +void Task::restartDelayed(unsigned long aDelay) { + enableDelayed(aDelay); + iIterations = iSetIterations; } +bool Task::isFirstIteration() { return (iRunCounter <= 1); } + +bool Task::isLastIteration() { return (iIterations == 0); } + +#ifdef _TASK_TIMECRITICAL + +long Task::getOverrun() { return iOverrun; } +long Task::getStartDelay() { return iStartDelay; } + +#endif // _TASK_TIMECRITICAL + + +#ifdef _TASK_WDT_IDS + +void Task::setId(unsigned int aID) { iTaskID = aID; } +unsigned int Task::getId() { return iTaskID; } +void Task::setControlPoint(unsigned int aPoint) { iControlPoint = aPoint; } +unsigned int Task::getControlPoint() { return iControlPoint; } + +#endif // _TASK_WDT_IDS + +#ifdef _TASK_LTS_POINTER + +void Task::setLtsPointer(void *aPtr) { iLTS = aPtr; } +void* Task::getLtsPointer() { return iLTS; } + +#endif // _TASK_LTS_POINTER + // ------------------ Scheduler implementation -------------------- /** Default constructor. * Creates a scheduler with an empty execution chain. */ -inline Scheduler::Scheduler() { - init(); +Scheduler::Scheduler() { + init(); +} + +/* +Scheduler::~Scheduler() { +#ifdef _TASK_SLEEP_ON_IDLE_RUN +#endif // _TASK_SLEEP_ON_IDLE_RUN } +*/ /** Initializes all internal varaibles */ -inline void Scheduler::init() { - iFirst = NULL; - iLast = NULL; - iCurrent = NULL; +void Scheduler::init() { + iFirst = NULL; + iLast = NULL; + iCurrent = NULL; + #ifdef _TASK_PRIORITY - iHighPriority = NULL; + iHighPriority = NULL; #endif // _TASK_PRIORITY + #ifdef _TASK_SLEEP_ON_IDLE_RUN - allowSleep(true); + allowSleep(true); #endif // _TASK_SLEEP_ON_IDLE_RUN } @@ -593,53 +694,58 @@ inline void Scheduler::init() { * @param &aTask - reference to the Task to be appended. * @note Task can only be part of the chain once. */ -inline void Scheduler::addTask(Task& aTask) { - - aTask.iScheduler = this; -// First task situation: - if (iFirst == NULL) { - iFirst = &aTask; - aTask.iPrev = NULL; - } - else { + void Scheduler::addTask(Task& aTask) { + +// Avoid adding task twice to the same scheduler + if (aTask.iScheduler == this) + return; + + aTask.iScheduler = this; +// First task situation: + if (iFirst == NULL) { + iFirst = &aTask; + aTask.iPrev = NULL; + } + else { // This task gets linked back to the previous last one - aTask.iPrev = iLast; - iLast->iNext = &aTask; - } + aTask.iPrev = iLast; + iLast->iNext = &aTask; + } // "Previous" last task gets linked to this one - as this one becomes the last one - aTask.iNext = NULL; - iLast = &aTask; + aTask.iNext = NULL; + iLast = &aTask; } /** Deletes specific Task from the execution chain * @param &aTask - reference to the task to be deleted from the chain */ -inline void Scheduler::deleteTask(Task& aTask) { - if (aTask.iPrev == NULL) { - if (aTask.iNext == NULL) { - iFirst = NULL; - iLast = NULL; - return; - } - else { - aTask.iNext->iPrev = NULL; - iFirst = aTask.iNext; - aTask.iNext = NULL; - return; - } - } - - if (aTask.iNext == NULL) { - aTask.iPrev->iNext = NULL; - iLast = aTask.iPrev; - aTask.iPrev = NULL; - return; - } - - aTask.iPrev->iNext = aTask.iNext; - aTask.iNext->iPrev = aTask.iPrev; - aTask.iPrev = NULL; - aTask.iNext = NULL; +void Scheduler::deleteTask(Task& aTask) { + aTask.iScheduler = NULL; + if (aTask.iPrev == NULL) { + if (aTask.iNext == NULL) { + iFirst = NULL; + iLast = NULL; + return; + } + else { + aTask.iNext->iPrev = NULL; + iFirst = aTask.iNext; + aTask.iNext = NULL; + return; + } + } + + if (aTask.iNext == NULL) { + aTask.iPrev->iNext = NULL; + iLast = aTask.iPrev; + aTask.iPrev = NULL; + return; + } + + aTask.iPrev->iNext = aTask.iNext; + aTask.iNext->iPrev = aTask.iPrev; + aTask.iPrev = NULL; + aTask.iNext = NULL; } /** Disables all tasks in the execution chain @@ -647,14 +753,15 @@ inline void Scheduler::deleteTask(Task& aTask) { * task remaining active is an error processing task * @param aRecursive - if true, tasks of the higher priority chains are disabled as well recursively */ -inline void Scheduler::disableAll(bool aRecursive) { - Task *current = iFirst; - while (current) { - current->disable(); - current = current->iNext; - } +void Scheduler::disableAll(bool aRecursive) { + Task *current = iFirst; + while (current) { + current->disable(); + current = current->iNext; + } + #ifdef _TASK_PRIORITY - if (aRecursive && iHighPriority) iHighPriority->disableAll(true); + if (aRecursive && iHighPriority) iHighPriority->disableAll(true); #endif // _TASK_PRIORITY } @@ -662,161 +769,249 @@ inline void Scheduler::disableAll(bool aRecursive) { /** Enables all the tasks in the execution chain * @param aRecursive - if true, tasks of the higher priority chains are enabled as well recursively */ -inline void Scheduler::enableAll(bool aRecursive) { - Task *current = iFirst; - while (current) { - current->enable(); - current = current->iNext; - } + void Scheduler::enableAll(bool aRecursive) { + Task *current = iFirst; + while (current) { + current->enable(); + current = current->iNext; + } + #ifdef _TASK_PRIORITY - if (aRecursive && iHighPriority) iHighPriority->enableAll(true); + if (aRecursive && iHighPriority) iHighPriority->enableAll(true); #endif // _TASK_PRIORITY + } /** Sets scheduler for the higher priority tasks (support for layered task priority) * @param aScheduler - pointer to a scheduler for the higher priority tasks */ #ifdef _TASK_PRIORITY -inline void Scheduler::setHighPriorityScheduler(Scheduler* aScheduler) { - if (aScheduler != this) iHighPriority = aScheduler; // Setting yourself as a higher priority one will create infinite recursive call +void Scheduler::setHighPriorityScheduler(Scheduler* aScheduler) { + if (aScheduler != this) iHighPriority = aScheduler; // Setting yourself as a higher priority one will create infinite recursive call + #ifdef _TASK_SLEEP_ON_IDLE_RUN - if (iHighPriority) { - iHighPriority->allowSleep(false); // Higher priority schedulers should not do power management - } + if (iHighPriority) { + iHighPriority->allowSleep(false); // Higher priority schedulers should not do power management + } #endif // _TASK_SLEEP_ON_IDLE_RUN + }; #endif // _TASK_PRIORITY #ifdef _TASK_SLEEP_ON_IDLE_RUN -inline void Scheduler::allowSleep(bool aState) { - iAllowSleep = aState; +void Scheduler::allowSleep(bool aState) { + iAllowSleep = aState; #ifdef ARDUINO_ARCH_ESP8266 - wifi_set_sleep_type( iAllowSleep ? LIGHT_SLEEP_T : NONE_SLEEP_T ); + wifi_set_sleep_type( iAllowSleep ? LIGHT_SLEEP_T : NONE_SLEEP_T ); #endif // ARDUINO_ARCH_ESP8266 +#ifdef ARDUINO_ARCH_ESP32 + // TO-DO; find a suitable replacement for ESP32 if possible. +#endif // ARDUINO_ARCH_ESP32 } #endif // _TASK_SLEEP_ON_IDLE_RUN -inline void Scheduler::startNow( bool aRecursive ) { - unsigned long t = _TASK_TIME_FUNCTION(); - - iCurrent = iFirst; - while (iCurrent) { - if ( iCurrent->iStatus.enabled ) iCurrent->iPreviousMillis = t - iCurrent->iDelay; - iCurrent = iCurrent->iNext; - } - +void Scheduler::startNow( bool aRecursive ) { + unsigned long t = _TASK_TIME_FUNCTION(); + + iCurrent = iFirst; + while (iCurrent) { + if ( iCurrent->iStatus.enabled ) iCurrent->iPreviousMillis = t - iCurrent->iDelay; + iCurrent = iCurrent->iNext; + } + #ifdef _TASK_PRIORITY - if (aRecursive && iHighPriority) iHighPriority->startNow( true ); + if (aRecursive && iHighPriority) iHighPriority->startNow( true ); #endif // _TASK_PRIORITY } +/** Returns number millis or micros until next scheduled iteration of a given task + * + * @param aTask - reference to task which next iteration is in question + */ +long Scheduler::timeUntilNextIteration(Task& aTask) { + +#ifdef _TASK_STATUS_REQUEST + + StatusRequest *s = aTask.getStatusRequest(); + if ( s != NULL && s->pending() ) + return (-1); // cannot be determined +#endif + if ( !aTask.isEnabled() ) + return (-1); // cannot be determined + + long d = (long) aTask.iDelay - ( (long) ((_TASK_TIME_FUNCTION() - aTask.iPreviousMillis)) ); + + if ( d < 0 ) + return (0); // Task will run as soon as possible + return ( d ); +} + +Task& Scheduler::currentTask() { return *iCurrent; } + +#ifdef _TASK_LTS_POINTER +void* Scheduler::currentLts() { return iCurrent->iLTS; } +#endif // _TASK_LTS_POINTER +#ifdef _TASK_TIMECRITICAL +bool Scheduler::isOverrun() { return (iCurrent->iOverrun < 0); } +#endif // _TASK_TIMECRITICAL + + + /** Makes one pass through the execution chain. * Tasks are executed in the order they were added to the chain * There is no concept of priority * Different pseudo "priority" could be achieved - * by running task more frequently + * by running task more frequently */ -inline bool Scheduler::execute() { - bool idleRun = true; - register unsigned long m, i; // millis, interval; +bool Scheduler::execute() { + bool idleRun = true; + register unsigned long m, i; // millis, interval; -#ifdef ARDUINO_ARCH_ESP8266 - unsigned long t1 = micros(); - unsigned long t2 = 0; +#ifdef _TASK_SLEEP_ON_IDLE_RUN +#if defined (ARDUINO_ARCH_ESP8266) || defined (ARDUINO_ARCH_ESP32) + unsigned long t1 = micros(); + unsigned long t2 = 0; #endif // ARDUINO_ARCH_ESP8266 +#endif // _TASK_SLEEP_ON_IDLE_RUN + + Task *nextTask; // support for deleting the task in the onDisable method + iCurrent = iFirst; - iCurrent = iFirst; - - while (iCurrent) { - #ifdef _TASK_PRIORITY - // If scheduler for higher priority tasks is set, it's entire chain is executed on every pass of the base scheduler - if (iHighPriority) idleRun = iHighPriority->execute() && idleRun; - iCurrentScheduler = this; + // If lower priority scheduler does not have a single task in the chain + // the higher priority scheduler still has to have a chance to run + if (!iCurrent && iHighPriority) iHighPriority->execute(); + iCurrentScheduler = this; #endif // _TASK_PRIORITY - do { - if ( iCurrent->iStatus.enabled ) { + + while (iCurrent) { + +#ifdef _TASK_PRIORITY + // If scheduler for higher priority tasks is set, it's entire chain is executed on every pass of the base scheduler + if (iHighPriority) idleRun = iHighPriority->execute() && idleRun; + iCurrentScheduler = this; +#endif // _TASK_PRIORITY + nextTask = iCurrent->iNext; + do { + if ( iCurrent->iStatus.enabled ) { #ifdef _TASK_WDT_IDS - // For each task the control points are initialized to avoid confusion because of carry-over: - iCurrent->iControlPoint = 0; + // For each task the control points are initialized to avoid confusion because of carry-over: + iCurrent->iControlPoint = 0; #endif // _TASK_WDT_IDS - - // Disable task on last iteration: - if (iCurrent->iIterations == 0) { + + // Disable task on last iteration: + if (iCurrent->iIterations == 0) { + iCurrent->disable(); + break; + } + m = _TASK_TIME_FUNCTION(); + i = iCurrent->iInterval; + +#ifdef _TASK_TIMEOUT + // Disable task on a timeout + if ( iCurrent->iTimeout && (m - iCurrent->iStarttime > iCurrent->iTimeout) ) { + iCurrent->iStatus.timeout = true; iCurrent->disable(); - break; + break; } - m = _TASK_TIME_FUNCTION(); - i = iCurrent->iInterval; +#endif // _TASK_TIMEOUT #ifdef _TASK_STATUS_REQUEST - // If StatusRequest object was provided, and still pending, and task is waiting, this task should not run - // Otherwise, continue with execution as usual. Tasks waiting to StatusRequest need to be rescheduled according to - // how they were placed into waiting state (waitFor or waitForDelayed) - if ( iCurrent->iStatus.waiting ) { - if ( (iCurrent->iStatusRequest)->pending() ) break; - if (iCurrent->iStatus.waiting == _TASK_SR_NODELAY) { - iCurrent->iPreviousMillis = m - (iCurrent->iDelay = i); - } - else { - iCurrent->iPreviousMillis = m; - } - iCurrent->iStatus.waiting = 0; - } + // If StatusRequest object was provided, and still pending, and task is waiting, this task should not run + // Otherwise, continue with execution as usual. Tasks waiting to StatusRequest need to be rescheduled according to + // how they were placed into waiting state (waitFor or waitForDelayed) + if ( iCurrent->iStatus.waiting ) { + if ( (iCurrent->iStatusRequest)->pending() ) break; + if (iCurrent->iStatus.waiting == _TASK_SR_NODELAY) { + iCurrent->iPreviousMillis = m - (iCurrent->iDelay = i); + } + else { + iCurrent->iPreviousMillis = m; + } + iCurrent->iStatus.waiting = 0; + } #endif // _TASK_STATUS_REQUEST - if ( m - iCurrent->iPreviousMillis < iCurrent->iDelay ) break; + if ( m - iCurrent->iPreviousMillis < iCurrent->iDelay ) break; - if ( iCurrent->iIterations > 0 ) iCurrent->iIterations--; // do not decrement (-1) being a signal of never-ending task - iCurrent->iRunCounter++; - iCurrent->iPreviousMillis += iCurrent->iDelay; + if ( iCurrent->iIterations > 0 ) iCurrent->iIterations--; // do not decrement (-1) being a signal of never-ending task + iCurrent->iRunCounter++; + iCurrent->iPreviousMillis += iCurrent->iDelay; #ifdef _TASK_TIMECRITICAL - // Updated_previous+current interval should put us into the future, so iOverrun should be positive or zero. - // If negative - the task is behind (next execution time is already in the past) - unsigned long p = iCurrent->iPreviousMillis; - iCurrent->iOverrun = (long) ( p + i - m ); - iCurrent->iStartDelay = (long) ( m - p ); + // Updated_previous+current interval should put us into the future, so iOverrun should be positive or zero. + // If negative - the task is behind (next execution time is already in the past) + unsigned long p = iCurrent->iPreviousMillis; + iCurrent->iOverrun = (long) ( p + i - m ); + iCurrent->iStartDelay = (long) ( m - p ); #endif // _TASK_TIMECRITICAL - iCurrent->iDelay = i; - if ( iCurrent->iCallback ) { - ( *(iCurrent->iCallback) )(); - idleRun = false; - } - } - } while (0); //guaranteed single run - allows use of "break" to exit - iCurrent = iCurrent->iNext; - } + iCurrent->iDelay = i; + +#ifdef _TASK_OO_CALLBACKS + idleRun = !iCurrent->Callback(); +#else + if ( iCurrent->iCallback ) { + iCurrent->iCallback(); + idleRun = false; + } +#endif // _TASK_OO_CALLBACKS + + } + } while (0); //guaranteed single run - allows use of "break" to exit + iCurrent = nextTask; +#if defined (ARDUINO_ARCH_ESP8266) || defined (ARDUINO_ARCH_ESP32) + yield(); +#endif // ARDUINO_ARCH_ESP8266 + } #ifdef _TASK_SLEEP_ON_IDLE_RUN - if (idleRun && iAllowSleep) { - -#ifdef ARDUINO_ARCH_AVR // Could be used only for AVR-based boards. - set_sleep_mode(SLEEP_MODE_IDLE); - sleep_enable(); - /* Now enter sleep mode. */ - sleep_mode(); - - /* The program will continue from here after the timer timeout ~1 ms */ - sleep_disable(); /* First thing to do is disable sleep. */ + if (idleRun && iAllowSleep) { + +#ifdef ARDUINO_ARCH_AVR // Could be used only for AVR-based boards. + set_sleep_mode(SLEEP_MODE_IDLE); + sleep_enable(); + /* Now enter sleep mode. */ + sleep_mode(); + + /* The program will continue from here after the timer timeout ~1 ms */ + sleep_disable(); /* First thing to do is disable sleep. */ #endif // ARDUINO_ARCH_AVR +#ifdef CORE_TEENSY + asm("wfi"); +#endif //CORE_TEENSY + #ifdef ARDUINO_ARCH_ESP8266 // to do: find suitable sleep function for esp8266 - t2 = micros() - t1; - if (t2 < _TASK_ESP8266_DLY_THRESHOLD) delay(1); // ESP8266 implementation of delay() uses timers and yield + t2 = micros() - t1; + if (t2 < _TASK_ESP8266_DLY_THRESHOLD) delay(1); // ESP8266 implementation of delay() uses timers and yield #endif // ARDUINO_ARCH_ESP8266 - } + +#ifdef ARDUINO_ARCH_ESP32 +//TODO: find a correct light sleep implementation for ESP32 + // esp_sleep_enable_timer_wakeup(1000); //1 ms + // int ret= esp_light_sleep_start(); + t2 = micros() - t1; + if (t2 < _TASK_ESP8266_DLY_THRESHOLD) delay(1); +#endif // ARDUINO_ARCH_ESP32 + +#ifdef ARDUINO_ARCH_STM32F1 + // Now go into stop mode, wake up on interrupt. + // Systick interrupt will run every 1 milliseconds. + asm(" wfi"); +#endif // ARDUINO_ARCH_STM32 + + } #endif // _TASK_SLEEP_ON_IDLE_RUN - return (idleRun); + return (idleRun); } diff --git a/src/TaskSchedulerDeclarations.h b/src/TaskSchedulerDeclarations.h new file mode 100644 index 0000000..96866e3 --- /dev/null +++ b/src/TaskSchedulerDeclarations.h @@ -0,0 +1,309 @@ +// Cooperative multitasking library for Arduino +// Copyright (c) 2015-2017 Anatoli Arkhipenko + +#include <stddef.h> +#include <stdint.h> + +#ifndef _TASKSCHEDULERDECLARATIONS_H_ +#define _TASKSCHEDULERDECLARATIONS_H_ + +// ---------------------------------------- +// The following "defines" control library functionality at compile time, +// and should be used in the main sketch depending on the functionality required +// +// #define _TASK_TIMECRITICAL // Enable monitoring scheduling overruns +// #define _TASK_SLEEP_ON_IDLE_RUN // Enable 1 ms SLEEP_IDLE powerdowns between tasks if no callback methods were invoked during the pass +// #define _TASK_STATUS_REQUEST // Compile with support for StatusRequest functionality - triggering tasks on status change events in addition to time only +// #define _TASK_WDT_IDS // Compile with support for wdt control points and task ids +// #define _TASK_LTS_POINTER // Compile with support for local task storage pointer +// #define _TASK_PRIORITY // Support for layered scheduling priority +// #define _TASK_MICRO_RES // Support for microsecond resolution +// #define _TASK_STD_FUNCTION // Support for std::function (ESP8266 and ESP32 ONLY) +// #define _TASK_DEBUG // Make all methods and variables public for debug purposes +// #define _TASK_INLINE // Make all methods "inline" - needed to support some multi-tab, multi-file implementations +// #define _TASK_TIMEOUT // Support for overall task timeout +// #define _TASK_OO_CALLBACKS // Support for dynamic callback method binding + +#ifdef _TASK_DEBUG + #define _TASK_SCOPE public +#else + #define _TASK_SCOPE private +#endif + +#define TASK_IMMEDIATE 0 +#define TASK_FOREVER (-1) +#define TASK_ONCE 1 + +#ifdef _TASK_TIMEOUT +#define TASK_NOTIMEOUT 0 +#endif + +#ifdef _TASK_PRIORITY + class Scheduler; + extern Scheduler* iCurrentScheduler; +#endif // _TASK_PRIORITY + +#if !defined(ARDUINO) + extern unsigned long micros(void); + extern unsigned long millis(void); +#endif + + +#ifdef _TASK_INLINE +#define INLINE inline +#else +#define INLINE +#endif + + +#ifndef _TASK_MICRO_RES + +#define TASK_MILLISECOND 1UL +#define TASK_SECOND 1000UL +#define TASK_MINUTE 60000UL +#define TASK_HOUR 3600000UL + +#else + +#define TASK_MILLISECOND 1000UL +#define TASK_SECOND 1000000UL +#define TASK_MINUTE 60000000UL +#define TASK_HOUR 3600000000UL + +#endif // _TASK_MICRO_RES + + +#ifdef _TASK_STATUS_REQUEST + +#define _TASK_SR_NODELAY 1 +#define _TASK_SR_DELAY 2 + +class StatusRequest { + public: + INLINE StatusRequest(); + INLINE void setWaiting(unsigned int aCount = 1); + INLINE bool signal(int aStatus = 0); + INLINE void signalComplete(int aStatus = 0); + INLINE bool pending(); + INLINE bool completed(); + INLINE int getStatus(); + INLINE int getCount(); + + _TASK_SCOPE: + unsigned int iCount; // number of statuses to wait for. waiting for more that 65000 events seems unreasonable: unsigned int should be sufficient + int iStatus; // status of the last completed request. negative = error; zero = OK; positive = OK with a specific status +}; +#endif // _TASK_STATUS_REQUEST + +#ifdef _TASK_STD_FUNCTION +#include <functional> +typedef std::function<void()> TaskCallback; +typedef std::function<void()> TaskOnDisable; +typedef std::function<bool()> TaskOnEnable; +#else +typedef void (*TaskCallback)(); +typedef void (*TaskOnDisable)(); +typedef bool (*TaskOnEnable)(); +#endif + +typedef struct { + bool enabled : 1; // indicates that task is enabled or not. + bool inonenable : 1; // indicates that task execution is inside OnEnable method (preventing infinite loops) + +#ifdef _TASK_STATUS_REQUEST + uint8_t waiting : 2; // indication if task is waiting on the status request +#endif + +#ifdef _TASK_TIMEOUT + bool timeout : 1; // indication if task is waiting on the status request +#endif + +} __task_status; + +class Scheduler; + + +class Task { + friend class Scheduler; + public: + +#ifdef _TASK_OO_CALLBACKS + INLINE Task(unsigned long aInterval=0, long aIterations=0, Scheduler* aScheduler=NULL, bool aEnable=false); +#else + INLINE Task(unsigned long aInterval=0, long aIterations=0, TaskCallback aCallback=NULL, Scheduler* aScheduler=NULL, bool aEnable=false, TaskOnEnable aOnEnable=NULL, TaskOnDisable aOnDisable=NULL); +#endif // _TASK_OO_CALLBACKS + + +#ifdef _TASK_STATUS_REQUEST +#ifdef _TASK_OO_CALLBACKS +// INLINE Task(Scheduler* aScheduler=NULL); + INLINE Task(Scheduler* aScheduler); +#else +// INLINE Task(TaskCallback aCallback=NULL, Scheduler* aScheduler=NULL, TaskOnEnable aOnEnable=NULL, TaskOnDisable aOnDisable=NULL); + INLINE Task(TaskCallback aCallback, Scheduler* aScheduler, TaskOnEnable aOnEnable=NULL, TaskOnDisable aOnDisable=NULL); +#endif // _TASK_OO_CALLBACKS +#endif // _TASK_STATUS_REQUEST + + INLINE ~Task(); + +#ifdef _TASK_TIMEOUT + INLINE void setTimeout(unsigned long aTimeout, bool aReset=false); + INLINE void resetTimeout(); + INLINE unsigned long getTimeout(); + INLINE long untilTimeout(); + INLINE bool timedOut(); +#endif + + INLINE void enable(); + INLINE bool enableIfNot(); + INLINE void enableDelayed(unsigned long aDelay=0); + INLINE void restart(); + INLINE void restartDelayed(unsigned long aDelay=0); + + INLINE void delay(unsigned long aDelay=0); + INLINE void forceNextIteration(); + INLINE bool disable(); + INLINE bool isEnabled(); + +#ifdef _TASK_OO_CALLBACKS + INLINE void set(unsigned long aInterval, long aIterations); +#else + INLINE void set(unsigned long aInterval, long aIterations, TaskCallback aCallback,TaskOnEnable aOnEnable=NULL, TaskOnDisable aOnDisable=NULL); +#endif // _TASK_OO_CALLBACKS + INLINE void setInterval(unsigned long aInterval); + INLINE unsigned long getInterval(); + INLINE void setIterations(long aIterations); + INLINE long getIterations(); + INLINE unsigned long getRunCounter() ; + +#ifdef _TASK_OO_CALLBACKS + virtual INLINE bool Callback() =0; // return true if run was "productive - this will disable sleep on the idle run for next pass + virtual INLINE bool OnEnable(); // return true if task should be enabled, false if it should remain disabled + virtual INLINE void OnDisable(); +#else + INLINE void setCallback(TaskCallback aCallback) ; + INLINE void setOnEnable(TaskOnEnable aCallback) ; + INLINE void setOnDisable(TaskOnDisable aCallback) ; + INLINE void yield(TaskCallback aCallback); + INLINE void yieldOnce(TaskCallback aCallback); +#endif // _TASK_OO_CALLBACKS + + INLINE bool isFirstIteration() ; + INLINE bool isLastIteration() ; + +#ifdef _TASK_TIMECRITICAL + INLINE long getOverrun() ; + INLINE long getStartDelay() ; +#endif // _TASK_TIMECRITICAL + +#ifdef _TASK_STATUS_REQUEST + INLINE void waitFor(StatusRequest* aStatusRequest, unsigned long aInterval = 0, long aIterations = 1); + INLINE void waitForDelayed(StatusRequest* aStatusRequest, unsigned long aInterval = 0, long aIterations = 1); + INLINE StatusRequest* getStatusRequest() ; + INLINE StatusRequest* getInternalStatusRequest() ; +#endif // _TASK_STATUS_REQUEST + +#ifdef _TASK_WDT_IDS + INLINE void setId(unsigned int aID) ; + INLINE unsigned int getId() ; + INLINE void setControlPoint(unsigned int aPoint) ; + INLINE unsigned int getControlPoint() ; +#endif // _TASK_WDT_IDS + +#ifdef _TASK_LTS_POINTER + INLINE void setLtsPointer(void *aPtr) ; + void* getLtsPointer() ; +#endif // _TASK_LTS_POINTER + + _TASK_SCOPE: + INLINE void reset(); + + volatile __task_status iStatus; + volatile unsigned long iInterval; // execution interval in milliseconds (or microseconds). 0 - immediate + volatile unsigned long iDelay; // actual delay until next execution (usually equal iInterval) + volatile unsigned long iPreviousMillis; // previous invocation time (millis). Next invocation = iPreviousMillis + iInterval. Delayed tasks will "catch up" + +#ifdef _TASK_TIMECRITICAL + volatile long iOverrun; // negative if task is "catching up" to it's schedule (next invocation time is already in the past) + volatile long iStartDelay; // actual execution of the task's callback method was delayed by this number of millis +#endif // _TASK_TIMECRITICAL + + volatile long iIterations; // number of iterations left. 0 - last iteration. -1 - infinite iterations + long iSetIterations; // number of iterations originally requested (for restarts) + unsigned long iRunCounter; // current number of iteration (starting with 1). Resets on enable. + +#ifndef _TASK_OO_CALLBACKS + TaskCallback iCallback; // pointer to the void callback method + TaskOnEnable iOnEnable; // pointer to the bool OnEnable callback method + TaskOnDisable iOnDisable; // pointer to the void OnDisable method +#endif // _TASK_OO_CALLBACKS + + Task *iPrev, *iNext; // pointers to the previous and next tasks in the chain + Scheduler *iScheduler; // pointer to the current scheduler + +#ifdef _TASK_STATUS_REQUEST + StatusRequest *iStatusRequest; // pointer to the status request task is or was waiting on + StatusRequest iMyStatusRequest; // internal Status request to let other tasks know of completion +#endif // _TASK_STATUS_REQUEST + +#ifdef _TASK_WDT_IDS + unsigned int iTaskID; // task ID (for debugging and watchdog identification) + unsigned int iControlPoint; // current control point within the callback method. Reset to 0 by scheduler at the beginning of each pass +#endif // _TASK_WDT_IDS + +#ifdef _TASK_LTS_POINTER + void *iLTS; // pointer to task's local storage. Needs to be recast to appropriate type (usually a struct). +#endif // _TASK_LTS_POINTER + +#ifdef _TASK_TIMEOUT + unsigned long iTimeout; // Task overall timeout + unsigned long iStarttime; // millis at task start time +#endif // _TASK_TIMEOUT +}; + +class Scheduler { + friend class Task; + public: + INLINE Scheduler(); +// ~Scheduler(); + INLINE void init(); + INLINE void addTask(Task& aTask); + INLINE void deleteTask(Task& aTask); + INLINE void disableAll(bool aRecursive = true); + INLINE void enableAll(bool aRecursive = true); + INLINE bool execute(); // Returns true if none of the tasks' callback methods was invoked (true = idle run) + INLINE void startNow(bool aRecursive = true); // reset ALL active tasks to immediate execution NOW. + INLINE Task& currentTask() ; + INLINE long timeUntilNextIteration(Task& aTask); // return number of ms until next iteration of a given Task + +#ifdef _TASK_SLEEP_ON_IDLE_RUN + INLINE void allowSleep(bool aState = true); +#endif // _TASK_SLEEP_ON_IDLE_RUN + +#ifdef _TASK_LTS_POINTER + INLINE void* currentLts(); +#endif // _TASK_LTS_POINTER + +#ifdef _TASK_TIMECRITICAL + INLINE bool isOverrun(); +#endif // _TASK_TIMECRITICAL + +#ifdef _TASK_PRIORITY + INLINE void setHighPriorityScheduler(Scheduler* aScheduler); + INLINE static Scheduler& currentScheduler() { return *(iCurrentScheduler); }; +#endif // _TASK_PRIORITY + + _TASK_SCOPE: + Task *iFirst, *iLast, *iCurrent; // pointers to first, last and current tasks in the chain + +#ifdef _TASK_SLEEP_ON_IDLE_RUN + bool iAllowSleep; // indication if putting avr to IDLE_SLEEP mode is allowed by the program at this time. +#endif // _TASK_SLEEP_ON_IDLE_RUN + +#ifdef _TASK_PRIORITY + Scheduler *iHighPriority; // Pointer to a higher priority scheduler +#endif // _TASK_PRIORITY +}; + + +#endif /* _TASKSCHEDULERDECLARATIONS_H_ */ |
