TaskScheduler.h 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. // Cooperative multitasking library for Arduino version 1.9.0
  2. // Copyright (c) 2015 Anatoli Arkhipenko
  3. //
  4. // Changelog:
  5. // v1.0.0:
  6. // 2015-02-24 - Initial release
  7. // 2015-02-28 - added delay() and disableOnLastIteration() methods
  8. // 2015-03-25 - changed scheduler execute() method for a more precise delay calculation:
  9. // 1. Do not delay if any of the tasks ran (making request for immediate execution redundant)
  10. // 2. Delay is invoked only if none of the tasks ran
  11. // 3. Delay is based on the min anticipated wait until next task _AND_ the runtime of execute method itself.
  12. // 2015-05-11 - added restart() and restartDelayed() methods to restart tasks which are on hold after running all iterations
  13. // 2015-05-19 - completely removed delay from the scheduler since there are no power saving there. using 1 ms sleep instead
  14. //
  15. // v1.4.1:
  16. // 2015-09-15 - more careful placement of AVR-specific includes for sleep method (compatibility with DUE)
  17. // sleep on idle run is no longer a default and should be explicitly compiled with _TASK_SLEEP_ON_IDLE_RUN defined
  18. //
  19. // v1.5.0:
  20. // 2015-09-20 - access to currently executing task (for callback methods)
  21. // 2015-09-20 - pass scheduler as a parameter to the task constructor to append the task to the end of the chain
  22. // 2015-09-20 - option to create a task already enabled
  23. //
  24. // v1.5.1:
  25. // 2015-09-21 - bug fix: incorrect handling of active tasks via set() and setIterations().
  26. // Thanks to Hannes Morgenstern for catching this one
  27. //
  28. // v1.6.0:
  29. // 2015-09-22 - revert back to having all tasks disable on last iteration.
  30. // 2015-09-22 - deprecated disableOnLastIteration method as a result
  31. // 2015-09-22 - created a separate branch 'disable-on-last-iteration' for this
  32. // 2015-10-01 - made version numbers semver compliant (documentation only)
  33. //
  34. // v1.7.0:
  35. // 2015-10-08 - introduced callback run counter - callback methods can branch on the iteration number.
  36. // 2015-10-11 - enableIfNot() - enable a task only if it is not already enabled. Returns true if was already enabled, false if was disabled.
  37. // 2015-10-11 - disable() returns previous enable state (true if was enabled, false if was already disabled)
  38. // 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
  39. // 2015-10-12 - new Task method: forceNextIteration() - makes next iteration happen immediately during the next pass regardless how much time is left
  40. //
  41. // v1.8.0:
  42. // 2015-10-13 - support for status request objects allowing tasks waiting on requests
  43. // 2015-10-13 - moved to a single header file to allow compilation control via #defines from the main sketch
  44. //
  45. // v1.8.1:
  46. // 2015-10-22 - implement Task id and control points to support identification of failure points for watchdog timer logging
  47. //
  48. // v1.8.2:
  49. // 2015-10-27 - implement Local Task Storage Pointer (allow use of same callback code for different tasks)
  50. // 2015-10-27 - bug: currentTask() method returns incorrect Task reference if called within OnEnable and OnDisable methods
  51. // 2015-10-27 - protection against infinite loop in OnEnable (if enable() methods are called within OnEnable)
  52. // 2015-10-29 - new currentLts() method in the scheduler class returns current task's LTS pointer in one call
  53. //
  54. // v1.8.3:
  55. // 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)
  56. // 2015-11-05 - implement waitForDelayed() method to allow task activation on the status request completion delayed for one current interval
  57. // 2015-11-09 - added callback methods prototypes to all examples for Arduino IDE 1.6.6 compatibility
  58. // 2015-11-14 - added several constants to be used as task parameters for readability (e.g, TASK_FOREVER, TASK_SECOND, etc.)
  59. // 2015-11-14 - significant optimization of the scheduler's execute loop, including millis() rollover fix option
  60. //
  61. // v1.8.4:
  62. // 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
  63. // 2015-11-16 - further optimizations of the task scheduler execute loop
  64. //
  65. // v1.8.5:
  66. // 2015-11-23 - bug fix: incorrect calculation of next task invocation in case callback changed the interval
  67. // 2015-11-23 - bug fix: Task::set() method calls setInterval() explicitly, therefore delaying the task in the same manner
  68. //
  69. // v1.9.0:
  70. // 2015-11-24 - packed three byte-long status variables into bit array structure data type - saving 2 bytes per each task instance
  71. /* ============================================
  72. Cooperative multitasking library code is placed under the MIT license
  73. Copyright (c) 2015 Anatoli Arkhipenko
  74. Permission is hereby granted, free of charge, to any person obtaining a copy
  75. of this software and associated documentation files (the "Software"), to deal
  76. in the Software without restriction, including without limitation the rights
  77. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  78. copies of the Software, and to permit persons to whom the Software is
  79. furnished to do so, subject to the following conditions:
  80. The above copyright notice and this permission notice shall be included in
  81. all copies or substantial portions of the Software.
  82. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  83. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  84. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  85. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  86. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  87. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  88. THE SOFTWARE.
  89. ===============================================
  90. */
  91. #include <Arduino.h>
  92. #ifndef _TASKSCHEDULER_H_
  93. #define _TASKSCHEDULER_H_
  94. /** ----------------------------------------
  95. * The following "defines" control library functionality at compile time,
  96. * and should be used in the main sketch depending on the functionality required
  97. *
  98. * #define _TASK_TIMECRITICAL // Enable monitoring scheduling overruns
  99. * #define _TASK_SLEEP_ON_IDLE_RUN // Enable 1 ms SLEEP_IDLE powerdowns between tasks if no callback methods were invoked during the pass
  100. * #define _TASK_STATUS_REQUEST // Compile with support for StatusRequest functionality - triggering tasks on status change events in addition to time only
  101. * #define _TASK_WDT_IDS // Compile with support for wdt control points and task ids
  102. * #define _TASK_LTS_POINTER // Compile with support for local task storage pointer
  103. * #define _TASK_ROLLOVER_FIX // Compensate for millis() rollover once every 47 days
  104. */
  105. #ifdef _TASK_SLEEP_ON_IDLE_RUN
  106. #include <avr/sleep.h>
  107. #include <avr/power.h>
  108. #endif
  109. #define TASK_IMMEDIATE 0
  110. #define TASK_SECOND 1000L
  111. #define TASK_MINUTE 60000L
  112. #define TASK_HOUR 3600000L
  113. #define TASK_FOREVER (-1)
  114. #define TASK_ONCE 1
  115. #ifdef _TASK_STATUS_REQUEST
  116. #define _TASK_SR_NODELAY 1
  117. #define _TASK_SR_DELAY 2
  118. class StatusRequest {
  119. public:
  120. StatusRequest() {iCount = 0; iStatus = 0; }
  121. inline void setWaiting(unsigned int aCount = 1) { iCount = aCount; iStatus = 0; }
  122. bool signal(int aStatus = 0);
  123. void signalComplete(int aStatus = 0);
  124. inline bool pending() { return (iCount != 0); }
  125. inline bool completed() { return (iCount == 0); }
  126. inline int getStatus() { return iStatus; }
  127. private:
  128. unsigned int iCount; // number of statuses to wait for. waiting for more that 65000 events seems unreasonable: unsigned int should be sufficient
  129. int iStatus; // status of the last completed request. negative = error; zero = OK; >positive = OK with a specific status
  130. };
  131. #endif
  132. typedef struct {
  133. bool enabled : 1; // indicates that task is enabled or not.
  134. bool inonenable : 1; // indicates that task execution is inside OnEnable method (preventing infinite loops)
  135. #ifdef _TASK_STATUS_REQUEST
  136. byte waiting : 2; // indication if task is waiting on the status request
  137. #endif
  138. } __task_status;
  139. class Scheduler;
  140. class Task {
  141. friend class Scheduler;
  142. public:
  143. Task(unsigned long aInterval=0, long aIterations=0, void (*aCallback)()=NULL, Scheduler* aScheduler=NULL, boolean aEnable=false, bool (*aOnEnable)()=NULL, void (*aOnDisable)()=NULL);
  144. #ifdef _TASK_STATUS_REQUEST
  145. Task(void (*aCallback)()=NULL, Scheduler* aScheduler=NULL, bool (*aOnEnable)()=NULL, void (*aOnDisable)()=NULL);
  146. #endif
  147. void enable();
  148. bool enableIfNot();
  149. void enableDelayed(unsigned long aDelay=0);
  150. void delay(unsigned long aDelay=0);
  151. void forceNextIteration();
  152. void restart();
  153. void restartDelayed(unsigned long aDelay=0);
  154. bool disable();
  155. inline bool isEnabled() { return iStatus.enabled; }
  156. void set(unsigned long aInterval, long aIterations, void (*aCallback)(),bool (*aOnEnable)()=NULL, void (*aOnDisable)()=NULL);
  157. void setInterval(unsigned long aInterval);
  158. inline unsigned long getInterval() { return iInterval; }
  159. void setIterations(long aIterations);
  160. inline long getIterations() { return iIterations; }
  161. inline unsigned long getRunCounter() { return iRunCounter; }
  162. inline void setCallback(void (*aCallback)()) { iCallback = aCallback; }
  163. inline void setOnEnable(bool (*aCallback)()) { iOnEnable = aCallback; }
  164. inline void setOnDisable(void (*aCallback)()) { iOnDisable = aCallback; }
  165. #ifdef _TASK_TIMECRITICAL
  166. inline long getOverrun() { return iOverrun; }
  167. #endif
  168. inline bool isFirstIteration() { return (iRunCounter <= 1); }
  169. inline bool isLastIteration() { return (iIterations == 0); }
  170. #ifdef _TASK_STATUS_REQUEST
  171. void waitFor(StatusRequest* aStatusRequest, unsigned long aInterval = 0, long aIterations = 1);
  172. void waitForDelayed(StatusRequest* aStatusRequest, unsigned long aInterval = 0, long aIterations = 1);
  173. inline StatusRequest* getStatusRequest() {return iStatusRequest; }
  174. #endif
  175. #ifdef _TASK_WDT_IDS
  176. inline void setId(unsigned int aID) { iTaskID = aID; }
  177. inline unsigned int getId() { return iTaskID; }
  178. inline void setControlPoint(unsigned int aPoint) { iControlPoint = aPoint; }
  179. inline unsigned int getControlPoint() { return iControlPoint; }
  180. #endif
  181. #ifdef _TASK_LTS_POINTER
  182. inline void setLtsPointer(void *aPtr) { iLTS = aPtr; }
  183. inline void* getLtsPointer() { return iLTS; }
  184. #endif
  185. private:
  186. void reset();
  187. volatile __task_status iStatus;
  188. volatile unsigned long iInterval; // execution interval in milliseconds. 0 - immediate
  189. volatile unsigned long iPreviousMillis; // previous invocation time (millis). Next invocation = iPreviousMillis + iInterval. Delayed tasks will "catch up"
  190. #ifdef _TASK_TIMECRITICAL
  191. volatile long iOverrun; // negative if task is "catching up" to it's schedule (next invocation time is already in the past)
  192. #endif
  193. volatile long iIterations; // number of iterations left. 0 - last iteration. -1 - infinite iterations
  194. long iSetIterations; // number of iterations originally requested (for restarts)
  195. unsigned long iRunCounter; // current number of iteration (starting with 1). Resets on enable.
  196. void (*iCallback)(); // pointer to the void callback method
  197. bool (*iOnEnable)(); // pointer to the bolol OnEnable callback method
  198. void (*iOnDisable)(); // pointer to the void OnDisable method
  199. Task *iPrev, *iNext; // pointers to the previous and next tasks in the chain
  200. Scheduler *iScheduler; // pointer to the current scheduler
  201. #ifdef _TASK_STATUS_REQUEST
  202. StatusRequest *iStatusRequest; // pointer to the status request task is or was waiting on
  203. #endif
  204. #ifdef _TASK_WDT_IDS
  205. unsigned int iTaskID; // task ID (for debugging and watchdog identification)
  206. unsigned int iControlPoint; // current control point within the callback method. Reset to 0 by scheduler at the beginning of each pass
  207. #endif
  208. #ifdef _TASK_LTS_POINTER
  209. void *iLTS; // pointer to task's local storage. Needs to be recast to appropriate type (usually a struct).
  210. #endif
  211. };
  212. class Scheduler {
  213. friend class Task;
  214. public:
  215. Scheduler();
  216. inline void init() { iFirst = NULL; iLast = NULL; iCurrent = NULL; }
  217. void addTask(Task& aTask);
  218. void deleteTask(Task& aTask);
  219. void disableAll();
  220. void enableAll();
  221. void execute();
  222. inline Task& currentTask() {return *iCurrent; }
  223. #ifdef _TASK_SLEEP_ON_IDLE_RUN
  224. void allowSleep(bool aState = true) { iAllowSleep = aState; }
  225. #endif
  226. #ifdef _TASK_LTS_POINTER
  227. inline void* currentLts() {return iCurrent->iLTS; }
  228. #endif
  229. #ifdef _TASK_TIMECRITICAL
  230. inline bool isOverrun() { return (iCurrent->iOverrun < 0); }
  231. #endif
  232. private:
  233. Task *iFirst, *iLast, *iCurrent; // pointers to first, last and current tasks in the chain
  234. #ifdef _TASK_SLEEP_ON_IDLE_RUN
  235. bool iAllowSleep; // indication if putting avr to IDLE_SLEEP mode is allowed by the program at this time.
  236. #endif
  237. };
  238. // ------------------ TaskScheduler implementation --------------------
  239. #ifdef _TASK_WDT_IDS
  240. static unsigned int __task_id_counter = 0; // global task ID counter for assiging task IDs automatically.
  241. #endif
  242. /** Constructor, uses default values for the parameters
  243. * so could be called with no parameters.
  244. */
  245. Task::Task( unsigned long aInterval, long aIterations, void (*aCallback)(), Scheduler* aScheduler, bool aEnable, bool (*aOnEnable)(), void (*aOnDisable)() ) {
  246. reset();
  247. set(aInterval, aIterations, aCallback, aOnEnable, aOnDisable);
  248. if (aScheduler) aScheduler->addTask(*this);
  249. #ifdef _TASK_STATUS_REQUEST
  250. iStatusRequest = NULL;
  251. #endif
  252. #ifdef _TASK_WDT_IDS
  253. iTaskID = ++__task_id_counter;
  254. #endif
  255. if (aEnable) enable();
  256. }
  257. #ifdef _TASK_STATUS_REQUEST
  258. /** Constructor with reduced parameter list for tasks created for
  259. * StatusRequest only triggering (always immediate and only 1 iteration)
  260. */
  261. Task::Task( void (*aCallback)(), Scheduler* aScheduler, bool (*aOnEnable)(), void (*aOnDisable)() ) {
  262. reset();
  263. set(TASK_IMMEDIATE, TASK_ONCE, aCallback, aOnEnable, aOnDisable);
  264. if (aScheduler) aScheduler->addTask(*this);
  265. iStatusRequest = NULL;
  266. #ifdef _TASK_WDT_IDS
  267. iTaskID = ++__task_id_counter;
  268. #endif
  269. }
  270. /** Signals completion of the StatusRequest by one of the participating events
  271. * @param: aStatus - if provided, sets the return code of the StatusRequest: negative = error, 0 (default) = OK, positive = OK with a specific status code
  272. * Negative status will complete Status Request fully (since an error occured).
  273. * @return: true, if StatusRequest is complete, false otherwise (still waiting for other events)
  274. */
  275. bool StatusRequest::signal(int aStatus) {
  276. if ( iCount) { // do not update the status request if it was already completed
  277. if (iCount > 0) --iCount;
  278. if ( (iStatus = aStatus) < 0 ) iCount = 0; // if an error is reported, the status is requested to be completed immediately
  279. }
  280. return (iCount == 0);
  281. }
  282. void StatusRequest::signalComplete(int aStatus) {
  283. if (iCount) { // do not update the status request if it was already completed
  284. iCount = 0;
  285. iStatus = aStatus;
  286. }
  287. }
  288. /** Sets a Task to wait until a particular event completes
  289. * @param: aStatusRequest - a pointer for the StatusRequest to wait for.
  290. * If aStatusRequest is NULL, request for waiting is ignored, and the waiting task is not enabled.
  291. */
  292. void Task::waitFor(StatusRequest* aStatusRequest, unsigned long aInterval, long aIterations) {
  293. if ( ( iStatusRequest = aStatusRequest) ) { // assign internal StatusRequest var and check if it is not NULL
  294. setIterations(aIterations);
  295. setInterval(aInterval);
  296. iStatus.waiting = _TASK_SR_NODELAY; // no delay
  297. enable();
  298. }
  299. }
  300. void Task::waitForDelayed(StatusRequest* aStatusRequest, unsigned long aInterval, long aIterations) {
  301. if ( ( iStatusRequest = aStatusRequest) ) { // assign internal StatusRequest var and check if it is not NULL
  302. setIterations(aIterations);
  303. if ( aInterval ) setInterval(aInterval); // For the dealyed version only set the interval if it was not a zero
  304. iStatus.waiting = _TASK_SR_DELAY; // with delay equal to the current interval
  305. enable();
  306. }
  307. }
  308. #endif
  309. /** Resets (initializes) the task/
  310. * Task is not enabled and is taken out
  311. * out of the execution chain as a result
  312. */
  313. void Task::reset() {
  314. iStatus.enabled = false;
  315. iStatus.inonenable = false;
  316. iPreviousMillis = 0;
  317. iPrev = NULL;
  318. iNext = NULL;
  319. iScheduler = NULL;
  320. iRunCounter = 0;
  321. #ifdef _TASK_TIMECRITICAL
  322. iOverrun = 0;
  323. #endif
  324. #ifdef _TASK_WDT_IDS
  325. iControlPoint = 0;
  326. #endif
  327. #ifdef _TASK_LTS_POINTER
  328. iLTS = NULL;
  329. #endif
  330. #ifdef _TASK_STATUS_REQUEST
  331. iStatus.waiting = 0;
  332. #endif
  333. }
  334. /** Explicitly set Task execution parameters
  335. * @param aInterval - execution interval in ms
  336. * @param aIterations - number of iterations, use -1 for no limit
  337. * @param aCallback - pointer to the callback method which executes the task actions
  338. * @param aOnEnable - pointer to the callback method which is called on enable()
  339. * @param aOnDisable - pointer to the callback method which is called on disable()
  340. */
  341. void Task::set(unsigned long aInterval, long aIterations, void (*aCallback)(),bool (*aOnEnable)(), void (*aOnDisable)()) {
  342. setInterval(aInterval);
  343. iSetIterations = iIterations = aIterations;
  344. iCallback = aCallback;
  345. iOnEnable = aOnEnable;
  346. iOnDisable = aOnDisable;
  347. }
  348. /** Sets number of iterations for the task
  349. * if task is enabled, schedule for immediate execution
  350. * @param aIterations - number of iterations, use -1 for no limit
  351. */
  352. void Task::setIterations(long aIterations) {
  353. iSetIterations = iIterations = aIterations;
  354. }
  355. /** Enables the task
  356. * schedules it for execution as soon as possible,
  357. * and resets the RunCounter back to zero
  358. */
  359. void Task::enable() {
  360. if (iScheduler) { // activation without active scheduler does not make sense
  361. iRunCounter = 0;
  362. if ( iOnEnable && !iStatus.inonenable ) {
  363. Task *current = iScheduler->iCurrent;
  364. iScheduler->iCurrent = this;
  365. iStatus.inonenable = true; // Protection against potential infinite loop
  366. iStatus.enabled = (*iOnEnable)();
  367. iStatus.inonenable = false; // Protection against potential infinite loop
  368. iScheduler->iCurrent = current;
  369. }
  370. else {
  371. iStatus.enabled = true;
  372. }
  373. iPreviousMillis = millis() - iInterval;
  374. }
  375. }
  376. /** Enables the task only if it was not enabled already
  377. * Returns previous state (true if was already enabled, false if was not)
  378. */
  379. bool Task::enableIfNot() {
  380. bool previousEnabled = iStatus.enabled;
  381. if ( !previousEnabled ) enable();
  382. return (previousEnabled);
  383. }
  384. /** Enables the task
  385. * and schedules it for execution after a delay = aInterval
  386. */
  387. void Task::enableDelayed(unsigned long aDelay) {
  388. enable();
  389. delay(aDelay);
  390. }
  391. /** Delays Task for execution after a delay = aInterval (if task is enabled).
  392. * leaves task enabled or disabled
  393. * if aDelay is zero, delays for the original scheduling interval from now
  394. */
  395. void Task::delay(unsigned long aDelay) {
  396. if (!aDelay) aDelay = iInterval;
  397. iPreviousMillis = millis() - iInterval + aDelay;
  398. }
  399. /** Schedules next iteration of Task for execution immediately (if enabled)
  400. * leaves task enabled or disabled
  401. * Task's original schedule is shifted, and all subsequent iterations will continue from this point in time
  402. */
  403. void Task::forceNextIteration() {
  404. iPreviousMillis = millis() - iInterval;
  405. }
  406. /** Sets the execution interval.
  407. * Task execution is delayed for aInterval
  408. * Use enable() to schedule execution ASAP
  409. * @param aInterval - new execution interval
  410. */
  411. void Task::setInterval (unsigned long aInterval) {
  412. iInterval = aInterval;
  413. delay();
  414. }
  415. /** Disables task
  416. * Task will no longer be executed by the scheduler
  417. * Returns status of the task before disable was called (i.e., if the task was already disabled)
  418. */
  419. bool Task::disable() {
  420. bool previousEnabled = iStatus.enabled;
  421. iStatus.enabled = false;
  422. iStatus.inonenable = false;
  423. if (previousEnabled && iOnDisable) {
  424. Task *current = iScheduler->iCurrent;
  425. iScheduler->iCurrent = this;
  426. (*iOnDisable)();
  427. iScheduler->iCurrent = current;
  428. }
  429. return (previousEnabled);
  430. }
  431. /** Restarts task
  432. * Task will run number of iterations again
  433. */
  434. void Task::restart() {
  435. iIterations = iSetIterations;
  436. enable();
  437. }
  438. /** Restarts task delayed
  439. * Task will run number of iterations again
  440. */
  441. void Task::restartDelayed(unsigned long aDelay) {
  442. iIterations = iSetIterations;
  443. enableDelayed(aDelay);
  444. }
  445. // ------------------ Scheduler implementation --------------------
  446. /** Default constructor.
  447. * Creates a scheduler with an empty execution chain.
  448. */
  449. Scheduler::Scheduler() {
  450. init();
  451. #ifdef _TASK_SLEEP_ON_IDLE_RUN
  452. iAllowSleep = true;
  453. #endif
  454. }
  455. /** Appends task aTask to the tail of the execution chain.
  456. * @param &aTask - reference to the Task to be appended.
  457. * @note Task can only be part of the chain once.
  458. */
  459. void Scheduler::addTask(Task& aTask) {
  460. aTask.iScheduler = this;
  461. // First task situation:
  462. if (iFirst == NULL) {
  463. iFirst = &aTask;
  464. aTask.iPrev = NULL;
  465. }
  466. else {
  467. // This task gets linked back to the previous last one
  468. aTask.iPrev = iLast;
  469. iLast->iNext = &aTask;
  470. }
  471. // "Previous" last task gets linked to this one - as this one becomes the last one
  472. aTask.iNext = NULL;
  473. iLast = &aTask;
  474. }
  475. /** Deletes specific Task from the execution chain
  476. * @param &aTask - reference to the task to be deleted from the chain
  477. */
  478. void Scheduler::deleteTask(Task& aTask) {
  479. if (aTask.iPrev == NULL) {
  480. if (aTask.iNext == NULL) {
  481. iFirst = NULL;
  482. iLast = NULL;
  483. return;
  484. }
  485. else {
  486. aTask.iNext->iPrev = NULL;
  487. iFirst = aTask.iNext;
  488. aTask.iNext = NULL;
  489. return;
  490. }
  491. }
  492. if (aTask.iNext == NULL) {
  493. aTask.iPrev->iNext = NULL;
  494. iLast = aTask.iPrev;
  495. aTask.iPrev = NULL;
  496. return;
  497. }
  498. aTask.iPrev->iNext = aTask.iNext;
  499. aTask.iNext->iPrev = aTask.iPrev;
  500. aTask.iPrev = NULL;
  501. aTask.iNext = NULL;
  502. }
  503. /** Disables all tasks in the execution chain
  504. * Convenient for error situations, when the only
  505. * task remaining active is an error processing task
  506. */
  507. void Scheduler::disableAll() {
  508. Task *current = iFirst;
  509. while (current) {
  510. current->disable();
  511. current = current->iNext;
  512. }
  513. }
  514. /** Enables all the tasks in the execution chain
  515. */
  516. void Scheduler::enableAll() {
  517. Task *current = iFirst;
  518. while (current) {
  519. current->enable();
  520. current = current->iNext;
  521. }
  522. }
  523. /** Makes one pass through the execution chain.
  524. * Tasks are executed in the order they were added to the chain
  525. * There is no concept of priority
  526. * Different pseudo "priority" could be achieved
  527. * by running task more frequently
  528. */
  529. void Scheduler::execute() {
  530. #ifdef _TASK_SLEEP_ON_IDLE_RUN
  531. bool idleRun = true;
  532. #endif
  533. unsigned long targetMillis;
  534. register unsigned long m, i, p;
  535. iCurrent = iFirst;
  536. while (iCurrent) {
  537. do {
  538. if ( iCurrent->iStatus.enabled ) {
  539. #ifdef _TASK_WDT_IDS
  540. // For each task the control points are initialized to avoid confusion because of carry-over:
  541. iCurrent->iControlPoint = 0;
  542. #endif
  543. // Disable task on last iteration:
  544. if (iCurrent->iIterations == 0) {
  545. iCurrent->disable();
  546. break;
  547. }
  548. m = millis();
  549. i = iCurrent->iInterval;
  550. #ifdef _TASK_STATUS_REQUEST
  551. // If StatusRequest object was provided, and still pending, and task is waiting, this task should not run
  552. // Otherwise, continue with execution as usual. Tasks waiting to StatusRequest need to be rescheduled according to
  553. // how they were placed into waiting state (waitFor or waitForDelayed)
  554. if ( iCurrent->iStatus.waiting ) {
  555. if ( (iCurrent->iStatusRequest)->pending() ) break;
  556. iCurrent->iPreviousMillis = (iCurrent->iStatus.waiting == _TASK_SR_NODELAY) ? m - i : m;
  557. iCurrent->iStatus.waiting = 0;
  558. }
  559. #endif
  560. p = iCurrent->iPreviousMillis;
  561. // Determine when current task is supposed to run
  562. // Once every 47 days there is a rollover execution which will occur due to millis and targetMillis rollovers
  563. // That is why there is an option to compile with rollover fix
  564. // Example
  565. // iPreviousMillis = 65000
  566. // iInterval = 600
  567. // millis() = 65500
  568. // targetMillis = 65000 + 600 = (should be 65600) 65 (due to rollover)
  569. // so 65 < 65500. should be 65600 > 65500. - task will be scheduled incorrectly
  570. // since targetMillis (65) < iPreviousMillis (65000), rollover fix kicks in:
  571. // iPreviousMillis(65000) > millis(65500) - iInterval(600) = 64900 - task will not be scheduled
  572. targetMillis = p + i;
  573. #ifdef _TASK_ROLLOVER_FIX
  574. if ( targetMillis < p ) { // targetMillis rolled over!
  575. if ( p > ( m - i) ) break;
  576. }
  577. else
  578. #endif
  579. if ( targetMillis > m ) break;
  580. #ifdef _TASK_TIMECRITICAL
  581. // Updated_previous+current interval should put us into the future, so iOverrun should be positive or zero.
  582. // If negative - the task is behind (next execution time is already in the past)
  583. iCurrent->iOverrun = (long) ( targetMillis - m + i );
  584. #endif
  585. if ( iCurrent->iIterations > 0 ) iCurrent->iIterations--; // do not decrement (-1) being a signal of never-ending task
  586. iCurrent->iRunCounter++;
  587. iCurrent->iPreviousMillis = targetMillis; //p + i
  588. if ( iCurrent->iCallback ) {
  589. ( *(iCurrent->iCallback) )();
  590. #ifdef _TASK_SLEEP_ON_IDLE_RUN
  591. idleRun = false;
  592. #endif
  593. }
  594. }
  595. } while (0); //guaranteed single run - allows use of "break" to exit
  596. iCurrent = iCurrent->iNext;
  597. }
  598. #ifdef _TASK_SLEEP_ON_IDLE_RUN
  599. if (idleRun && iAllowSleep) {
  600. set_sleep_mode(SLEEP_MODE_IDLE);
  601. sleep_enable();
  602. /* Now enter sleep mode. */
  603. sleep_mode();
  604. /* The program will continue from here after the timer timeout ~1 ms */
  605. sleep_disable(); /* First thing to do is disable sleep. */
  606. }
  607. #endif
  608. }
  609. #endif /* _TASKSCHEDULER_H_ */