aboutsummaryrefslogtreecommitdiff
path: root/src/TaskScheduler.h
diff options
context:
space:
mode:
authorAnatoli Arkhipenko <arkhipenko@hotmail.com>2015-11-14 17:46:16 -0500
committerAnatoli Arkhipenko <arkhipenko@hotmail.com>2015-11-14 17:46:16 -0500
commitdc152db48e325a5d03be0d48a07c119d97d11e59 (patch)
tree72fdcd4c9feee8da743e9540ce58580e7392a7d9 /src/TaskScheduler.h
parentef2329bdeade723f560cd0f7e933c9f3119677f3 (diff)
Version 1.8.3 number of addtions and improvementsv1.8.3
* support for task activation on a status request with arbitrary interval and number of iterations (0 and 1 are still default values) * implement waitForDelayed() method to allow task activation on the status request completion delayed for one current interval * added callback methods prototypes to all examples for Arduino IDE 1.6.6 compatibility * added several constants to be used as task parameters for readability (e.g, TASK_FOREVER, TASK_SECOND, etc.) * significant optimization of the scheduler's execute loop, including millis() rollover fix option
Diffstat (limited to 'src/TaskScheduler.h')
-rw-r--r--src/TaskScheduler.h213
1 files changed, 129 insertions, 84 deletions
diff --git a/src/TaskScheduler.h b/src/TaskScheduler.h
index b1f3ca0..228a8d0 100644
--- a/src/TaskScheduler.h
+++ b/src/TaskScheduler.h
@@ -50,6 +50,13 @@
// 2015-10-27 - bug: currentTask() method returns incorrect Task reference if called within OnEnable and OnDisable methods
// 2015-10-27 - protection against infinite loop in OnEnable (if enable() methods are called within OnEnable)
// 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-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
/* ============================================
@@ -92,6 +99,7 @@ THE SOFTWARE.
* #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_ROLLOVER_FIX // Compensate for millis() rollover once every 47 days
*/
#ifdef _TASK_SLEEP_ON_IDLE_RUN
@@ -99,8 +107,18 @@ THE SOFTWARE.
#include <avr/power.h>
#endif
+#define TASK_IMMEDIATE 0
+#define TASK_SECOND 1000L
+#define TASK_MINUTE 60000L
+#define TASK_HOUR 3600000L
+#define TASK_FOREVER (-1)
+#define TASK_ONCE 1
#ifdef _TASK_STATUS_REQUEST
+
+#define _TASK_SR_NODELAY 1
+#define _TASK_SR_DELAY 2
+
class StatusRequest {
public:
StatusRequest() {iCount = 0; iStatus = 0; }
@@ -113,7 +131,7 @@ class StatusRequest {
private:
unsigned int iCount; // waiting for more that 65000 events seems unreasonable: unsigned int should be sufficient
- int iStatus; // negative = error; zero = OK; >positive = OK with a specific status
+ int iStatus; // negative = error; zero = OK; >positive = OK with a specific status
};
#endif
@@ -123,76 +141,78 @@ class Scheduler;
class Task {
friend class Scheduler;
public:
- Task(unsigned long aInterval=0, long aIterations=0, void (*aCallback)()=NULL, Scheduler* aScheduler=NULL, boolean aEnable=false, bool (*aOnEnable)()=NULL, void (*aOnDisable)()=NULL);
+ Task(unsigned long aInterval=0, long aIterations=0, void (*aCallback)()=NULL, Scheduler* aScheduler=NULL, boolean 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);
+ Task(void (*aCallback)()=NULL, Scheduler* aScheduler=NULL, bool (*aOnEnable)()=NULL, void (*aOnDisable)()=NULL);
#endif
- 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 iEnabled; }
- 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; }
+ 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 iEnabled; }
+ 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 getOverrun() { return iOverrun; }
#endif
- inline bool isFirstIteration() { return (iRunCounter <= 1); }
- inline bool isLastIteration() { return (iIterations == 0); }
+ inline bool isFirstIteration() { return (iRunCounter <= 1); }
+ inline bool isLastIteration() { return (iIterations == 0); }
#ifdef _TASK_STATUS_REQUEST
- void waitFor(StatusRequest* aStatusRequest);
- inline StatusRequest* getStatusRequest() {return iStatusRequest; }
+ 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
#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; }
+ 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
#ifdef _TASK_LTS_POINTER
- inline void setLtsPointer(void *aPtr) { iLTS = aPtr; }
- inline void* getLtsPointer() { return iLTS; }
+ inline void setLtsPointer(void *aPtr) { iLTS = aPtr; }
+ inline void* getLtsPointer() { return iLTS; }
#endif
private:
- void reset();
+ void reset();
- volatile bool iEnabled;
- bool iInOnEnable;
- volatile unsigned long iInterval;
- volatile unsigned long iPreviousMillis;
+ volatile bool iEnabled;
+ volatile bool iInOnEnable;
+ volatile unsigned long iInterval;
+ volatile unsigned long iPreviousMillis;
#ifdef _TASK_TIMECRITICAL
- volatile long iOverrun;
+ volatile long iOverrun;
#endif
- volatile long iIterations;
- long iSetIterations;
- unsigned long iRunCounter;
- void (*iCallback)();
- bool (*iOnEnable)();
- void (*iOnDisable)();
- Task *iPrev, *iNext;
- Scheduler *iScheduler;
+ volatile long iIterations;
+ long iSetIterations;
+ unsigned long iRunCounter;
+ void (*iCallback)();
+ bool (*iOnEnable)();
+ void (*iOnDisable)();
+ Task *iPrev, *iNext;
+ Scheduler *iScheduler;
#ifdef _TASK_STATUS_REQUEST
- StatusRequest *iStatusRequest;
+ StatusRequest *iStatusRequest;
+ byte iWaiting;
#endif
#ifdef _TASK_WDT_IDS
- unsigned int iTaskID;
- unsigned int iControlPoint;
+ unsigned int iTaskID;
+ unsigned int iControlPoint;
#endif
#ifdef _TASK_LTS_POINTER
- void *iLTS;
+ void *iLTS;
#endif
};
@@ -213,6 +233,9 @@ class Scheduler {
#ifdef _TASK_LTS_POINTER
inline void* currentLts() {return iCurrent->iLTS; }
#endif
+#ifdef _TASK_TIMECRITICAL
+ inline bool isOverrun() { return (iCurrent->iOverrun < 0); }
+#endif
private:
Task *iFirst, *iLast, *iCurrent;
@@ -282,14 +305,23 @@ void StatusRequest::signalComplete(int aStatus) {
* @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.
*/
-void Task::waitFor(StatusRequest* aStatusRequest) {
+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(1);
- setInterval(0);
+ setIterations(aIterations);
+ setInterval(aInterval);
+ iWaiting = _TASK_SR_NODELAY; // no delay
enable();
}
}
+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
+ iWaiting = _TASK_SR_DELAY; // with delay equal to the current interval
+ enable();
+ }
+}
#endif
/** Resets (initializes) the task/
@@ -312,6 +344,9 @@ void Task::reset() {
#ifdef _TASK_LTS_POINTER
iLTS = NULL;
#endif
+#ifdef _TASK_STATUS_REQUEST
+ iWaiting = 0;
+#endif
}
/** Explicitly set Task execution parameters
@@ -533,6 +568,7 @@ void Scheduler::execute() {
#ifdef _TASK_SLEEP_ON_IDLE_RUN
bool idleRun = true;
#endif
+ unsigned long targetMillis;
iCurrent = iFirst;
@@ -540,50 +576,59 @@ void Scheduler::execute() {
do {
if (iCurrent->iEnabled) {
#ifdef _TASK_WDT_IDS
- // For each task the control points are initialized to avoid confusion because of carry-over
+ // For each task the control points are initialized to avoid confusion because of carry-over:
iCurrent->iControlPoint = 0;
#endif
+
+ // Disable task on last iteration:
if (iCurrent->iIterations == 0) {
iCurrent->disable();
break;
}
#ifdef _TASK_STATUS_REQUEST
- // If StatusRequest object was provided (not NULL) and it is still bending, this task should not run
- // Otherwise, continue with execution as usual. Tasks waiting to StatusRequest usually have interval = 0, and iterations = 1
- // so they will run once immediately.
- if ( iCurrent->iStatusRequest && (iCurrent->iStatusRequest)->pending() ) break;
+ // 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->iWaiting ) {
+ if ( (iCurrent->iStatusRequest)->pending() ) break;
+ iCurrent->iPreviousMillis = (iCurrent->iWaiting == _TASK_SR_NODELAY) ? millis()-iCurrent->iInterval : millis();
+ iCurrent->iWaiting = 0;
+ }
#endif
- if ( iCurrent->iInterval > 0 ) {
- unsigned long targetMillis = iCurrent->iPreviousMillis + iCurrent->iInterval;
- if ( targetMillis <= millis() ) {
- if ( iCurrent->iIterations > 0 ) iCurrent->iIterations--; // do not decrement (-1) being a signal of eternal task
- iCurrent->iRunCounter++;
- iCurrent->iPreviousMillis += iCurrent->iInterval;
-
+ // Determine when current task is supposed to run
+ // Once every 47 days there is a rollover execution which will occur due to millis and targetMillis rollovers
+ // That is why there is an option to compile with rollover fix
+ // Example
+ // iPreviousMillis = 65000
+ // iInterval = 600
+ // millis() = 65500
+ // targetMillis = 65000 + 600 = (should be 65600) 65 (due to rollover)
+ // so 65 < 65500. should be 65600 > 65500. - task will be scheduled incorrectly
+ // since targetMillis (65) < iPreviousMillis (65000), rollover fix kicks in:
+ // iPreviousMillis(65000) > millis(65500) - iInterval(600) = 64900 - task will not be scheduled
+
+ targetMillis = iCurrent->iPreviousMillis + iCurrent->iInterval;
+ #ifdef _TASK_ROLLOVER_FIX
+ if ( targetMillis < iCurrent->iPreviousMillis ) { // targetMillis rolled over!
+ if ( iCurrent->iPreviousMillis > ( millis() - iCurrent->iInterval) ) break;
+ }
+ else
+ #endif
+ if ( targetMillis > millis() ) break;
+
+ iCurrent->iPreviousMillis = targetMillis;
#ifdef _TASK_TIMECRITICAL
- // Updated_previous+current should put us into the future, so iOverrun should be positive or zero.
+ // 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)
- iCurrent->iOverrun = (long) (iCurrent->iPreviousMillis + iCurrent->iInterval - millis());
+ iCurrent->iOverrun = (long) (iCurrent->iPreviousMillis + iCurrent->iInterval - millis());
#endif
-
- if ( iCurrent->iCallback ) {
- ( *(iCurrent->iCallback) )();
- #ifdef _TASK_SLEEP_ON_IDLE_RUN
- idleRun = false;
- #endif
- }
- break;
- }
- }
- else {
- if ( iCurrent->iIterations > 0 ) iCurrent->iIterations--; // do not decrement (-1) being a signal of eternal task
- iCurrent->iRunCounter++;
- if ( iCurrent->iCallback ) {
- ( *(iCurrent->iCallback) )();
+ if ( iCurrent->iIterations > 0 ) iCurrent->iIterations--; // do not decrement (-1) being a signal of never-ending task
+ iCurrent->iRunCounter++;
+ if ( iCurrent->iCallback ) {
+ ( *(iCurrent->iCallback) )();
#ifdef _TASK_SLEEP_ON_IDLE_RUN
- idleRun = false;
+ idleRun = false;
#endif
- }
}
}
} while (0); //guaranteed single run - allows use of "break" to exit