TaskScheduler.h 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002
  1. // Cooperative multitasking library for Arduino
  2. // Copyright (c) 2015-2017 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
  18. // _TASK_SLEEP_ON_IDLE_RUN defined
  19. //
  20. // v1.5.0:
  21. // 2015-09-20 - access to currently executing task (for callback methods)
  22. // 2015-09-20 - pass scheduler as a parameter to the task constructor to append the task to the end of the chain
  23. // 2015-09-20 - option to create a task already enabled
  24. //
  25. // v1.5.1:
  26. // 2015-09-21 - bug fix: incorrect handling of active tasks via set() and setIterations().
  27. // Thanks to Hannes Morgenstern for catching this one
  28. //
  29. // v1.6.0:
  30. // 2015-09-22 - revert back to having all tasks disable on last iteration.
  31. // 2015-09-22 - deprecated disableOnLastIteration method as a result
  32. // 2015-09-22 - created a separate branch 'disable-on-last-iteration' for this
  33. // 2015-10-01 - made version numbers semver compliant (documentation only)
  34. //
  35. // v1.7.0:
  36. // 2015-10-08 - introduced callback run counter - callback methods can branch on the iteration number.
  37. // 2015-10-11 - enableIfNot() - enable a task only if it is not already enabled. Returns true if was already enabled,
  38. // false if was disabled.
  39. // 2015-10-11 - disable() returns previous enable state (true if was enabled, false if was already disabled)
  40. // 2015-10-11 - introduced callback methods "on enable" and "on disable". On enable runs every time enable is called,
  41. // on disable runs only if task was enabled
  42. // 2015-10-12 - new Task method: forceNextIteration() - makes next iteration happen immediately during the next pass
  43. // regardless how much time is left
  44. //
  45. // v1.8.0:
  46. // 2015-10-13 - support for status request objects allowing tasks waiting on requests
  47. // 2015-10-13 - moved to a single header file to allow compilation control via #defines from the main sketch
  48. //
  49. // v1.8.1:
  50. // 2015-10-22 - implement Task id and control points to support identification of failure points for watchdog timer logging
  51. //
  52. // v1.8.2:
  53. // 2015-10-27 - implement Local Task Storage Pointer (allow use of same callback code for different tasks)
  54. // 2015-10-27 - bug: currentTask() method returns incorrect Task reference if called within OnEnable and OnDisable methods
  55. // 2015-10-27 - protection against infinite loop in OnEnable (if enable() methods are called within OnEnable)
  56. // 2015-10-29 - new currentLts() method in the scheduler class returns current task's LTS pointer in one call
  57. //
  58. // v1.8.3:
  59. // 2015-11-05 - support for task activation on a status request with arbitrary interval and number of iterations
  60. // (0 and 1 are still default values)
  61. // 2015-11-05 - implement waitForDelayed() method to allow task activation on the status request completion
  62. // delayed for one current interval
  63. // 2015-11-09 - added callback methods prototypes to all examples for Arduino IDE 1.6.6 compatibility
  64. // 2015-11-14 - added several constants to be used as task parameters for readability (e.g, TASK_FOREVER, TASK_SECOND, etc.)
  65. // 2015-11-14 - significant optimization of the scheduler's execute loop, including millis() rollover fix option
  66. //
  67. // v1.8.4:
  68. // 2015-11-15 - bug fix: Task alignment with millis() for scheduling purposes should be done after OnEnable, not before.
  69. // Especially since OnEnable method can change the interval
  70. // 2015-11-16 - further optimizations of the task scheduler execute loop
  71. //
  72. // v1.8.5:
  73. // 2015-11-23 - bug fix: incorrect calculation of next task invocation in case callback changed the interval
  74. // 2015-11-23 - bug fix: Task::set() method calls setInterval() explicitly, therefore delaying the task in the same manner
  75. //
  76. // v1.9.0:
  77. // 2015-11-24 - packed three byte-long status variables into bit array structure data type - saving 2 bytes per each task instance
  78. //
  79. // v1.9.2:
  80. // 2015-11-28 - _TASK_ROLLOVER_FIX is deprecated (not necessary)
  81. // 2015-12-16 - bug fixes: automatic millis rollover support for delay methods
  82. // 2015-12-17 - new method for _TASK_TIMECRITICAL option: getStartDelay()
  83. //
  84. // v2.0.0:
  85. // 2015-12-22 - _TASK_PRIORITY - support for layered task prioritization
  86. //
  87. // v2.0.1:
  88. // 2016-01-02 - bug fix: issue#11 Xtensa compiler (esp8266): Declaration of constructor does not match implementation
  89. //
  90. // v2.0.2:
  91. // 2016-01-05 - bug fix: time constants wrapped inside compile option
  92. // 2016-01-05 - support for ESP8266 wifi power saving mode for _TASK_SLEEP_ON_IDLE_RUN compile option
  93. //
  94. // v2.1.0:
  95. // 2016-02-01 - support for microsecond resolution
  96. // 2016-02-02 - added Scheduler baseline start time reset method: startNow()
  97. //
  98. // v2.2.0:
  99. // 2016-11-17 - all methods made 'inline' to support inclusion of TaskSchedule.h file into other header files
  100. //
  101. // v2.2.1:
  102. // 2016-11-30 - inlined constructors. Added "yield()" and "yieldOnce()" functions to easily break down and chain
  103. // back together long running callback methods
  104. // 2016-12-16 - added "getCount()" to StatusRequest objects, made every task StatusRequest enabled.
  105. // Internal StatusRequest objects are accessible via "getInternalStatusRequest()" method.
  106. //
  107. // v2.3.0:
  108. // 2017-02-24 - new timeUntilNextIteration() method within Scheduler class - inquire when a particlar task is
  109. // scheduled to run next time
  110. //
  111. // v2.4.0:
  112. // 2017-04-27 - added destructor to the Task class to ensure tasks are disables and taken off the execution chain
  113. // upon destruction. (Contributed by Edwin van Leeuwen [BlackEdder - https://github.com/BlackEdder)
  114. //
  115. // v2.5.0:
  116. // 2017-04-27 - ESP8266 ONLY: added optional support for std::functions via _TASK_STD_FUNCTION compilation option
  117. // (Contributed by Edwin van Leeuwen [BlackEdder - https://github.com/BlackEdder)
  118. // 2017-08-30 - add _TASK_DEBUG making all methods and variables public FOR DEBUGGING PURPOSES ONLY!
  119. // Use at your own risk!
  120. // 2017-08-30 - bug fix: Scheduler::addTask() checks if task is already part of an execution chain (github issue #37)
  121. // 2017-08-30 - support for multi-tab sketches (Contributed by Adam Ryczkowski - https://github.com/adamryczkowski)
  122. //
  123. // v2.5.1:
  124. // 2018-01-06 - support for IDLE sleep on Teensy boards (tested on Teensy 3.5)
  125. //
  126. // v2.5.2:
  127. // 2018-01-09 - _TASK_INLINE compilation directive making all methods declared "inline" (issue #42)
  128. //
  129. // v2.6.0:
  130. // 2018-01-30 - _TASK_TIMEOUT compilation directive: Task overall timeout functionality
  131. // 2018-01-30 - ESP32 support (experimental)
  132. // (Contributed by Marco Tombesi: https://github.com/baggior)
  133. //
  134. // v2.6.1:
  135. // 2018-02-13 - Bug: support for task self-destruction in the OnDisable method
  136. // Example 19: dynamic tasks creation and destruction
  137. // 2018-03-14 - Bug: high level scheduler ignored if lower level chain is empty
  138. // Example 20: use of local task storage to work with task-specific class objects
  139. //
  140. // v3.0.0:
  141. // 2018-03-15 - Major Release: Support for dynamic callback methods binding via compilation parameter _TASK_OO_CALLBACKS
  142. //
  143. // v.3.0.1:
  144. // 2018-11-09 - bug: task deleted from the execution chain cannot be added back
  145. #include <Arduino.h>
  146. #include "TaskSchedulerDeclarations.h"
  147. #ifndef _TASKSCHEDULER_H_
  148. #define _TASKSCHEDULER_H_
  149. // ----------------------------------------
  150. // The following "defines" control library functionality at compile time,
  151. // and should be used in the main sketch depending on the functionality required
  152. //
  153. // #define _TASK_TIMECRITICAL // Enable monitoring scheduling overruns
  154. // #define _TASK_SLEEP_ON_IDLE_RUN // Enable 1 ms SLEEP_IDLE powerdowns between tasks if no callback methods were invoked during the pass
  155. // #define _TASK_STATUS_REQUEST // Compile with support for StatusRequest functionality - triggering tasks on status change events in addition to time only
  156. // #define _TASK_WDT_IDS // Compile with support for wdt control points and task ids
  157. // #define _TASK_LTS_POINTER // Compile with support for local task storage pointer
  158. // #define _TASK_PRIORITY // Support for layered scheduling priority
  159. // #define _TASK_MICRO_RES // Support for microsecond resolution
  160. // #define _TASK_STD_FUNCTION // Support for std::function (ESP8266 ONLY)
  161. // #define _TASK_DEBUG // Make all methods and variables public for debug purposes
  162. // #define _TASK_INLINE // Make all methods "inline" - needed to support some multi-tab, multi-file implementations
  163. // #define _TASK_TIMEOUT // Support for overall task timeout
  164. // #define _TASK_OO_CALLBACKS // Support for callbacks via inheritance
  165. #ifdef _TASK_MICRO_RES
  166. #undef _TASK_SLEEP_ON_IDLE_RUN // SLEEP_ON_IDLE has only millisecond resolution
  167. #define _TASK_TIME_FUNCTION() micros()
  168. #else
  169. #define _TASK_TIME_FUNCTION() millis()
  170. #endif // _TASK_MICRO_RES
  171. #ifdef _TASK_SLEEP_ON_IDLE_RUN
  172. #ifdef ARDUINO_ARCH_AVR
  173. #include <avr/sleep.h>
  174. #include <avr/power.h>
  175. #endif // ARDUINO_ARCH_AVR
  176. #ifdef ARDUINO_ARCH_ESP8266
  177. #define _TASK_ESP8266_DLY_THRESHOLD 200L
  178. extern "C" {
  179. #include "user_interface.h"
  180. }
  181. #endif //ARDUINO_ARCH_ESP8266
  182. #ifdef ARDUINO_ARCH_ESP32
  183. #define _TASK_ESP8266_DLY_THRESHOLD 200L
  184. #warning _TASK_SLEEP_ON_IDLE_RUN for ESP32 cannot use light sleep mode but a standard delay for 1 ms
  185. #endif // ARDUINO_ARCH_ESP32
  186. #endif // _TASK_SLEEP_ON_IDLE_RUN
  187. #if !defined (ARDUINO_ARCH_ESP8266) && !defined (ARDUINO_ARCH_ESP32)
  188. #ifdef _TASK_STD_FUNCTION
  189. #error Support for std::function only for ESP8266 or ESP32 architecture
  190. #undef _TASK_STD_FUNCTION
  191. #endif // _TASK_STD_FUNCTION
  192. #endif // ARDUINO_ARCH_ESP8266
  193. #ifdef _TASK_WDT_IDS
  194. static unsigned int __task_id_counter = 0; // global task ID counter for assiging task IDs automatically.
  195. #endif // _TASK_WDT_IDS
  196. #ifdef _TASK_PRIORITY
  197. Scheduler* iCurrentScheduler;
  198. #endif // _TASK_PRIORITY
  199. // ------------------ TaskScheduler implementation --------------------
  200. /** Constructor, uses default values for the parameters
  201. * so could be called with no parameters.
  202. */
  203. #ifdef _TASK_OO_CALLBACKS
  204. Task::Task( unsigned long aInterval, long aIterations, Scheduler* aScheduler, bool aEnable ) {
  205. reset();
  206. set(aInterval, aIterations);
  207. #else
  208. Task::Task( unsigned long aInterval, long aIterations, TaskCallback aCallback, Scheduler* aScheduler, bool aEnable, TaskOnEnable aOnEnable, TaskOnDisable aOnDisable ) {
  209. reset();
  210. set(aInterval, aIterations, aCallback, aOnEnable, aOnDisable);
  211. #endif
  212. if (aScheduler) aScheduler->addTask(*this);
  213. #ifdef _TASK_WDT_IDS
  214. iTaskID = ++__task_id_counter;
  215. #endif // _TASK_WDT_IDS
  216. if (aEnable) enable();
  217. }
  218. /** Destructor.
  219. * Makes sure the task disabled and deleted out of the chain
  220. * prior to being deleted.
  221. */
  222. Task::~Task() {
  223. disable();
  224. if (iScheduler)
  225. iScheduler->deleteTask(*this);
  226. }
  227. #ifdef _TASK_STATUS_REQUEST
  228. /** Constructor with reduced parameter list for tasks created for
  229. * StatusRequest only triggering (always immediate and only 1 iteration)
  230. */
  231. #ifdef _TASK_OO_CALLBACKS
  232. Task::Task( Scheduler* aScheduler ) {
  233. reset();
  234. set(TASK_IMMEDIATE, TASK_ONCE);
  235. #else
  236. Task::Task( TaskCallback aCallback, Scheduler* aScheduler, TaskOnEnable aOnEnable, TaskOnDisable aOnDisable ) {
  237. reset();
  238. set(TASK_IMMEDIATE, TASK_ONCE, aCallback, aOnEnable, aOnDisable);
  239. #endif // _TASK_OO_CALLBACKS
  240. if (aScheduler) aScheduler->addTask(*this);
  241. #ifdef _TASK_WDT_IDS
  242. iTaskID = ++__task_id_counter;
  243. #endif // _TASK_WDT_IDS
  244. }
  245. StatusRequest::StatusRequest()
  246. {
  247. iCount = 0;
  248. iStatus = 0;
  249. }
  250. void StatusRequest::setWaiting(unsigned int aCount) { iCount = aCount; iStatus = 0; }
  251. bool StatusRequest::pending() { return (iCount != 0); }
  252. bool StatusRequest::completed() { return (iCount == 0); }
  253. int StatusRequest::getStatus() { return iStatus; }
  254. int StatusRequest::getCount() { return iCount; }
  255. StatusRequest* Task::getStatusRequest() { return iStatusRequest; }
  256. StatusRequest* Task::getInternalStatusRequest() { return &iMyStatusRequest; }
  257. /** Signals completion of the StatusRequest by one of the participating events
  258. * @param: aStatus - if provided, sets the return code of the StatusRequest: negative = error, 0 (default) = OK, positive = OK with a specific status code
  259. * Negative status will complete Status Request fully (since an error occured).
  260. * @return: true, if StatusRequest is complete, false otherwise (still waiting for other events)
  261. */
  262. bool StatusRequest::signal(int aStatus) {
  263. if ( iCount) { // do not update the status request if it was already completed
  264. if (iCount > 0) --iCount;
  265. if ( (iStatus = aStatus) < 0 ) iCount = 0; // if an error is reported, the status is requested to be completed immediately
  266. }
  267. return (iCount == 0);
  268. }
  269. void StatusRequest::signalComplete(int aStatus) {
  270. if (iCount) { // do not update the status request if it was already completed
  271. iCount = 0;
  272. iStatus = aStatus;
  273. }
  274. }
  275. /** Sets a Task to wait until a particular event completes
  276. * @param: aStatusRequest - a pointer for the StatusRequest to wait for.
  277. * If aStatusRequest is NULL, request for waiting is ignored, and the waiting task is not enabled.
  278. */
  279. void Task::waitFor(StatusRequest* aStatusRequest, unsigned long aInterval, long aIterations) {
  280. if ( ( iStatusRequest = aStatusRequest) ) { // assign internal StatusRequest var and check if it is not NULL
  281. setIterations(aIterations);
  282. setInterval(aInterval);
  283. iStatus.waiting = _TASK_SR_NODELAY; // no delay
  284. enable();
  285. }
  286. }
  287. void Task::waitForDelayed(StatusRequest* aStatusRequest, unsigned long aInterval, long aIterations) {
  288. if ( ( iStatusRequest = aStatusRequest) ) { // assign internal StatusRequest var and check if it is not NULL
  289. setIterations(aIterations);
  290. if ( aInterval ) setInterval(aInterval); // For the dealyed version only set the interval if it was not a zero
  291. iStatus.waiting = _TASK_SR_DELAY; // with delay equal to the current interval
  292. enable();
  293. }
  294. }
  295. #endif // _TASK_STATUS_REQUEST
  296. bool Task::isEnabled() { return iStatus.enabled; }
  297. unsigned long Task::getInterval() { return iInterval; }
  298. long Task::getIterations() { return iIterations; }
  299. unsigned long Task::getRunCounter() { return iRunCounter; }
  300. #ifdef _TASK_OO_CALLBACKS
  301. // bool Task::Callback() { return true; }
  302. bool Task::OnEnable() { return true; }
  303. void Task::OnDisable() { }
  304. #else
  305. void Task::setCallback(TaskCallback aCallback) { iCallback = aCallback; }
  306. void Task::setOnEnable(TaskOnEnable aCallback) { iOnEnable = aCallback; }
  307. void Task::setOnDisable(TaskOnDisable aCallback) { iOnDisable = aCallback; }
  308. #endif // _TASK_OO_CALLBACKS
  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. iInterval = iDelay = 0;
  318. iPrev = NULL;
  319. iNext = NULL;
  320. iScheduler = NULL;
  321. iRunCounter = 0;
  322. #ifdef _TASK_TIMECRITICAL
  323. iOverrun = 0;
  324. iStartDelay = 0;
  325. #endif // _TASK_TIMECRITICAL
  326. #ifdef _TASK_WDT_IDS
  327. iControlPoint = 0;
  328. #endif // _TASK_WDT_IDS
  329. #ifdef _TASK_LTS_POINTER
  330. iLTS = NULL;
  331. #endif // _TASK_LTS_POINTER
  332. #ifdef _TASK_STATUS_REQUEST
  333. iStatusRequest = NULL;
  334. iStatus.waiting = 0;
  335. iMyStatusRequest.signalComplete();
  336. #endif // _TASK_STATUS_REQUEST
  337. #ifdef _TASK_TIMEOUT
  338. iTimeout = 0;
  339. iStarttime = 0;
  340. iStatus.timeout = false;
  341. #endif // _TASK_TIMEOUT
  342. }
  343. /** Explicitly set Task execution parameters
  344. * @param aInterval - execution interval in ms
  345. * @param aIterations - number of iterations, use -1 for no limit
  346. * @param aCallback - pointer to the callback method which executes the task actions
  347. * @param aOnEnable - pointer to the callback method which is called on enable()
  348. * @param aOnDisable - pointer to the callback method which is called on disable()
  349. */
  350. #ifdef _TASK_OO_CALLBACKS
  351. void Task::set(unsigned long aInterval, long aIterations) {
  352. #else
  353. void Task::set(unsigned long aInterval, long aIterations, TaskCallback aCallback, TaskOnEnable aOnEnable, TaskOnDisable aOnDisable) {
  354. iCallback = aCallback;
  355. iOnEnable = aOnEnable;
  356. iOnDisable = aOnDisable;
  357. #endif // _TASK_OO_CALLBACKS
  358. setInterval(aInterval);
  359. iSetIterations = iIterations = aIterations;
  360. }
  361. /** Sets number of iterations for the task
  362. * if task is enabled, schedule for immediate execution
  363. * @param aIterations - number of iterations, use -1 for no limit
  364. */
  365. void Task::setIterations(long aIterations) {
  366. iSetIterations = iIterations = aIterations;
  367. }
  368. #ifndef _TASK_OO_CALLBACKS
  369. /** Prepare task for next step iteration following yielding of control to the scheduler
  370. * @param aCallback - pointer to the callback method for the next step
  371. */
  372. void Task::yield (TaskCallback aCallback) {
  373. iCallback = aCallback;
  374. forceNextIteration();
  375. // The next 2 lines adjust runcounter and number of iterations
  376. // as if it is the same run of the callback, just split between
  377. // a series of callback methods
  378. iRunCounter--;
  379. if ( iIterations >= 0 ) iIterations++;
  380. }
  381. /** Prepare task for next step iteration following yielding of control to the scheduler
  382. * @param aCallback - pointer to the callback method for the next step
  383. */
  384. void Task::yieldOnce (TaskCallback aCallback) {
  385. yield(aCallback);
  386. iIterations = 1;
  387. }
  388. #endif // _TASK_OO_CALLBACKS
  389. /** Enables the task
  390. * schedules it for execution as soon as possible,
  391. * and resets the RunCounter back to zero
  392. */
  393. void Task::enable() {
  394. if (iScheduler) { // activation without active scheduler does not make sense
  395. iRunCounter = 0;
  396. #ifdef _TASK_OO_CALLBACKS
  397. if ( !iStatus.inonenable ) {
  398. Task *current = iScheduler->iCurrent;
  399. iScheduler->iCurrent = this;
  400. iStatus.inonenable = true; // Protection against potential infinite loop
  401. iStatus.enabled = OnEnable();
  402. iStatus.inonenable = false; // Protection against potential infinite loop
  403. iScheduler->iCurrent = current;
  404. }
  405. #else
  406. if ( iOnEnable && !iStatus.inonenable ) {
  407. Task *current = iScheduler->iCurrent;
  408. iScheduler->iCurrent = this;
  409. iStatus.inonenable = true; // Protection against potential infinite loop
  410. iStatus.enabled = iOnEnable();
  411. iStatus.inonenable = false; // Protection against potential infinite loop
  412. iScheduler->iCurrent = current;
  413. }
  414. else {
  415. iStatus.enabled = true;
  416. }
  417. #endif // _TASK_OO_CALLBACKS
  418. iPreviousMillis = _TASK_TIME_FUNCTION() - (iDelay = iInterval);
  419. #ifdef _TASK_TIMEOUT
  420. resetTimeout();
  421. #endif // _TASK_TIMEOUT
  422. #ifdef _TASK_STATUS_REQUEST
  423. if ( iStatus.enabled ) {
  424. iMyStatusRequest.setWaiting();
  425. }
  426. #endif // _TASK_STATUS_REQUEST
  427. }
  428. }
  429. /** Enables the task only if it was not enabled already
  430. * Returns previous state (true if was already enabled, false if was not)
  431. */
  432. bool Task::enableIfNot() {
  433. bool previousEnabled = iStatus.enabled;
  434. if ( !previousEnabled ) enable();
  435. return (previousEnabled);
  436. }
  437. /** Enables the task
  438. * and schedules it for execution after a delay = aInterval
  439. */
  440. void Task::enableDelayed(unsigned long aDelay) {
  441. enable();
  442. delay(aDelay);
  443. }
  444. #ifdef _TASK_TIMEOUT
  445. void Task::setTimeout(unsigned long aTimeout, bool aReset) {
  446. iTimeout = aTimeout;
  447. if (aReset) resetTimeout();
  448. }
  449. void Task::resetTimeout() {
  450. iStarttime = _TASK_TIME_FUNCTION();
  451. iStatus.timeout = false;
  452. }
  453. unsigned long Task::getTimeout() {
  454. return iTimeout;
  455. }
  456. long Task::untilTimeout() {
  457. if ( iTimeout ) {
  458. return ( (long) (iStarttime + iTimeout) - (long) _TASK_TIME_FUNCTION() );
  459. }
  460. return -1;
  461. }
  462. bool Task::timedOut() {
  463. return iStatus.timeout;
  464. }
  465. #endif // _TASK_TIMEOUT
  466. /** Delays Task for execution after a delay = aInterval (if task is enabled).
  467. * leaves task enabled or disabled
  468. * if aDelay is zero, delays for the original scheduling interval from now
  469. */
  470. void Task::delay(unsigned long aDelay) {
  471. // if (!aDelay) aDelay = iInterval;
  472. iDelay = aDelay ? aDelay : iInterval;
  473. iPreviousMillis = _TASK_TIME_FUNCTION(); // - iInterval + aDelay;
  474. }
  475. /** Schedules next iteration of Task for execution immediately (if enabled)
  476. * leaves task enabled or disabled
  477. * Task's original schedule is shifted, and all subsequent iterations will continue from this point in time
  478. */
  479. void Task::forceNextIteration() {
  480. iPreviousMillis = _TASK_TIME_FUNCTION() - (iDelay = iInterval);
  481. }
  482. /** Sets the execution interval.
  483. * Task execution is delayed for aInterval
  484. * Use enable() to schedule execution ASAP
  485. * @param aInterval - new execution interval
  486. */
  487. void Task::setInterval (unsigned long aInterval) {
  488. iInterval = aInterval;
  489. delay(); // iDelay will be updated by the delay() function
  490. }
  491. /** Disables task
  492. * Task will no longer be executed by the scheduler
  493. * Returns status of the task before disable was called (i.e., if the task was already disabled)
  494. */
  495. bool Task::disable() {
  496. bool previousEnabled = iStatus.enabled;
  497. iStatus.enabled = false;
  498. iStatus.inonenable = false;
  499. #ifdef _TASK_OO_CALLBACKS
  500. if (previousEnabled) {
  501. #else
  502. if (previousEnabled && iOnDisable) {
  503. #endif // _TASK_OO_CALLBACKS
  504. Task *current = iScheduler->iCurrent;
  505. iScheduler->iCurrent = this;
  506. #ifdef _TASK_OO_CALLBACKS
  507. OnDisable();
  508. #else
  509. iOnDisable();
  510. #endif // _TASK_OO_CALLBACKS
  511. iScheduler->iCurrent = current;
  512. }
  513. #ifdef _TASK_STATUS_REQUEST
  514. iMyStatusRequest.signalComplete();
  515. #endif
  516. return (previousEnabled);
  517. }
  518. /** Restarts task
  519. * Task will run number of iterations again
  520. */
  521. void Task::restart() {
  522. enable();
  523. iIterations = iSetIterations;
  524. }
  525. /** Restarts task delayed
  526. * Task will run number of iterations again
  527. */
  528. void Task::restartDelayed(unsigned long aDelay) {
  529. enableDelayed(aDelay);
  530. iIterations = iSetIterations;
  531. }
  532. bool Task::isFirstIteration() { return (iRunCounter <= 1); }
  533. bool Task::isLastIteration() { return (iIterations == 0); }
  534. #ifdef _TASK_TIMECRITICAL
  535. long Task::getOverrun() { return iOverrun; }
  536. long Task::getStartDelay() { return iStartDelay; }
  537. #endif // _TASK_TIMECRITICAL
  538. #ifdef _TASK_WDT_IDS
  539. void Task::setId(unsigned int aID) { iTaskID = aID; }
  540. unsigned int Task::getId() { return iTaskID; }
  541. void Task::setControlPoint(unsigned int aPoint) { iControlPoint = aPoint; }
  542. unsigned int Task::getControlPoint() { return iControlPoint; }
  543. #endif // _TASK_WDT_IDS
  544. #ifdef _TASK_LTS_POINTER
  545. void Task::setLtsPointer(void *aPtr) { iLTS = aPtr; }
  546. void* Task::getLtsPointer() { return iLTS; }
  547. #endif // _TASK_LTS_POINTER
  548. // ------------------ Scheduler implementation --------------------
  549. /** Default constructor.
  550. * Creates a scheduler with an empty execution chain.
  551. */
  552. Scheduler::Scheduler() {
  553. init();
  554. }
  555. /*
  556. Scheduler::~Scheduler() {
  557. #ifdef _TASK_SLEEP_ON_IDLE_RUN
  558. #endif // _TASK_SLEEP_ON_IDLE_RUN
  559. }
  560. */
  561. /** Initializes all internal varaibles
  562. */
  563. void Scheduler::init() {
  564. iFirst = NULL;
  565. iLast = NULL;
  566. iCurrent = NULL;
  567. #ifdef _TASK_PRIORITY
  568. iHighPriority = NULL;
  569. #endif // _TASK_PRIORITY
  570. #ifdef _TASK_SLEEP_ON_IDLE_RUN
  571. allowSleep(true);
  572. #endif // _TASK_SLEEP_ON_IDLE_RUN
  573. }
  574. /** Appends task aTask to the tail of the execution chain.
  575. * @param &aTask - reference to the Task to be appended.
  576. * @note Task can only be part of the chain once.
  577. */
  578. void Scheduler::addTask(Task& aTask) {
  579. // Avoid adding task twice to the same scheduler
  580. if (aTask.iScheduler == this)
  581. return;
  582. aTask.iScheduler = this;
  583. // First task situation:
  584. if (iFirst == NULL) {
  585. iFirst = &aTask;
  586. aTask.iPrev = NULL;
  587. }
  588. else {
  589. // This task gets linked back to the previous last one
  590. aTask.iPrev = iLast;
  591. iLast->iNext = &aTask;
  592. }
  593. // "Previous" last task gets linked to this one - as this one becomes the last one
  594. aTask.iNext = NULL;
  595. iLast = &aTask;
  596. }
  597. /** Deletes specific Task from the execution chain
  598. * @param &aTask - reference to the task to be deleted from the chain
  599. */
  600. void Scheduler::deleteTask(Task& aTask) {
  601. aTask.iScheduler = NULL;
  602. if (aTask.iPrev == NULL) {
  603. if (aTask.iNext == NULL) {
  604. iFirst = NULL;
  605. iLast = NULL;
  606. return;
  607. }
  608. else {
  609. aTask.iNext->iPrev = NULL;
  610. iFirst = aTask.iNext;
  611. aTask.iNext = NULL;
  612. return;
  613. }
  614. }
  615. if (aTask.iNext == NULL) {
  616. aTask.iPrev->iNext = NULL;
  617. iLast = aTask.iPrev;
  618. aTask.iPrev = NULL;
  619. return;
  620. }
  621. aTask.iPrev->iNext = aTask.iNext;
  622. aTask.iNext->iPrev = aTask.iPrev;
  623. aTask.iPrev = NULL;
  624. aTask.iNext = NULL;
  625. }
  626. /** Disables all tasks in the execution chain
  627. * Convenient for error situations, when the only
  628. * task remaining active is an error processing task
  629. * @param aRecursive - if true, tasks of the higher priority chains are disabled as well recursively
  630. */
  631. void Scheduler::disableAll(bool aRecursive) {
  632. Task *current = iFirst;
  633. while (current) {
  634. current->disable();
  635. current = current->iNext;
  636. }
  637. #ifdef _TASK_PRIORITY
  638. if (aRecursive && iHighPriority) iHighPriority->disableAll(true);
  639. #endif // _TASK_PRIORITY
  640. }
  641. /** Enables all the tasks in the execution chain
  642. * @param aRecursive - if true, tasks of the higher priority chains are enabled as well recursively
  643. */
  644. void Scheduler::enableAll(bool aRecursive) {
  645. Task *current = iFirst;
  646. while (current) {
  647. current->enable();
  648. current = current->iNext;
  649. }
  650. #ifdef _TASK_PRIORITY
  651. if (aRecursive && iHighPriority) iHighPriority->enableAll(true);
  652. #endif // _TASK_PRIORITY
  653. }
  654. /** Sets scheduler for the higher priority tasks (support for layered task priority)
  655. * @param aScheduler - pointer to a scheduler for the higher priority tasks
  656. */
  657. #ifdef _TASK_PRIORITY
  658. void Scheduler::setHighPriorityScheduler(Scheduler* aScheduler) {
  659. if (aScheduler != this) iHighPriority = aScheduler; // Setting yourself as a higher priority one will create infinite recursive call
  660. #ifdef _TASK_SLEEP_ON_IDLE_RUN
  661. if (iHighPriority) {
  662. iHighPriority->allowSleep(false); // Higher priority schedulers should not do power management
  663. }
  664. #endif // _TASK_SLEEP_ON_IDLE_RUN
  665. };
  666. #endif // _TASK_PRIORITY
  667. #ifdef _TASK_SLEEP_ON_IDLE_RUN
  668. void Scheduler::allowSleep(bool aState) {
  669. iAllowSleep = aState;
  670. #ifdef ARDUINO_ARCH_ESP8266
  671. wifi_set_sleep_type( iAllowSleep ? LIGHT_SLEEP_T : NONE_SLEEP_T );
  672. #endif // ARDUINO_ARCH_ESP8266
  673. #ifdef ARDUINO_ARCH_ESP32
  674. // TO-DO; find a suitable replacement for ESP32 if possible.
  675. #endif // ARDUINO_ARCH_ESP32
  676. }
  677. #endif // _TASK_SLEEP_ON_IDLE_RUN
  678. void Scheduler::startNow( bool aRecursive ) {
  679. unsigned long t = _TASK_TIME_FUNCTION();
  680. iCurrent = iFirst;
  681. while (iCurrent) {
  682. if ( iCurrent->iStatus.enabled ) iCurrent->iPreviousMillis = t - iCurrent->iDelay;
  683. iCurrent = iCurrent->iNext;
  684. }
  685. #ifdef _TASK_PRIORITY
  686. if (aRecursive && iHighPriority) iHighPriority->startNow( true );
  687. #endif // _TASK_PRIORITY
  688. }
  689. /** Returns number millis or micros until next scheduled iteration of a given task
  690. *
  691. * @param aTask - reference to task which next iteration is in question
  692. */
  693. long Scheduler::timeUntilNextIteration(Task& aTask) {
  694. #ifdef _TASK_STATUS_REQUEST
  695. StatusRequest *s = aTask.getStatusRequest();
  696. if ( s != NULL && s->pending() )
  697. return (-1); // cannot be determined
  698. #endif
  699. if ( !aTask.isEnabled() )
  700. return (-1); // cannot be determined
  701. long d = (long) aTask.iDelay - ( (long) ((_TASK_TIME_FUNCTION() - aTask.iPreviousMillis)) );
  702. if ( d < 0 )
  703. return (0); // Task will run as soon as possible
  704. return ( d );
  705. }
  706. Task& Scheduler::currentTask() { return *iCurrent; }
  707. #ifdef _TASK_LTS_POINTER
  708. void* Scheduler::currentLts() { return iCurrent->iLTS; }
  709. #endif // _TASK_LTS_POINTER
  710. #ifdef _TASK_TIMECRITICAL
  711. bool Scheduler::isOverrun() { return (iCurrent->iOverrun < 0); }
  712. #endif // _TASK_TIMECRITICAL
  713. /** Makes one pass through the execution chain.
  714. * Tasks are executed in the order they were added to the chain
  715. * There is no concept of priority
  716. * Different pseudo "priority" could be achieved
  717. * by running task more frequently
  718. */
  719. bool Scheduler::execute() {
  720. bool idleRun = true;
  721. register unsigned long m, i; // millis, interval;
  722. #ifdef _TASK_SLEEP_ON_IDLE_RUN
  723. #if defined (ARDUINO_ARCH_ESP8266) || defined (ARDUINO_ARCH_ESP32)
  724. unsigned long t1 = micros();
  725. unsigned long t2 = 0;
  726. #endif // ARDUINO_ARCH_ESP8266
  727. #endif // _TASK_SLEEP_ON_IDLE_RUN
  728. Task *nextTask; // support for deleting the task in the onDisable method
  729. iCurrent = iFirst;
  730. #ifdef _TASK_PRIORITY
  731. // If lower priority scheduler does not have a single task in the chain
  732. // the higher priority scheduler still has to have a chance to run
  733. if (!iCurrent && iHighPriority) iHighPriority->execute();
  734. iCurrentScheduler = this;
  735. #endif // _TASK_PRIORITY
  736. while (iCurrent) {
  737. #ifdef _TASK_PRIORITY
  738. // If scheduler for higher priority tasks is set, it's entire chain is executed on every pass of the base scheduler
  739. if (iHighPriority) idleRun = iHighPriority->execute() && idleRun;
  740. iCurrentScheduler = this;
  741. #endif // _TASK_PRIORITY
  742. nextTask = iCurrent->iNext;
  743. do {
  744. if ( iCurrent->iStatus.enabled ) {
  745. #ifdef _TASK_WDT_IDS
  746. // For each task the control points are initialized to avoid confusion because of carry-over:
  747. iCurrent->iControlPoint = 0;
  748. #endif // _TASK_WDT_IDS
  749. // Disable task on last iteration:
  750. if (iCurrent->iIterations == 0) {
  751. iCurrent->disable();
  752. break;
  753. }
  754. m = _TASK_TIME_FUNCTION();
  755. i = iCurrent->iInterval;
  756. #ifdef _TASK_TIMEOUT
  757. // Disable task on a timeout
  758. if ( iCurrent->iTimeout && (m - iCurrent->iStarttime > iCurrent->iTimeout) ) {
  759. iCurrent->iStatus.timeout = true;
  760. iCurrent->disable();
  761. break;
  762. }
  763. #endif // _TASK_TIMEOUT
  764. #ifdef _TASK_STATUS_REQUEST
  765. // If StatusRequest object was provided, and still pending, and task is waiting, this task should not run
  766. // Otherwise, continue with execution as usual. Tasks waiting to StatusRequest need to be rescheduled according to
  767. // how they were placed into waiting state (waitFor or waitForDelayed)
  768. if ( iCurrent->iStatus.waiting ) {
  769. if ( (iCurrent->iStatusRequest)->pending() ) break;
  770. if (iCurrent->iStatus.waiting == _TASK_SR_NODELAY) {
  771. iCurrent->iPreviousMillis = m - (iCurrent->iDelay = i);
  772. }
  773. else {
  774. iCurrent->iPreviousMillis = m;
  775. }
  776. iCurrent->iStatus.waiting = 0;
  777. }
  778. #endif // _TASK_STATUS_REQUEST
  779. if ( m - iCurrent->iPreviousMillis < iCurrent->iDelay ) break;
  780. if ( iCurrent->iIterations > 0 ) iCurrent->iIterations--; // do not decrement (-1) being a signal of never-ending task
  781. iCurrent->iRunCounter++;
  782. iCurrent->iPreviousMillis += iCurrent->iDelay;
  783. #ifdef _TASK_TIMECRITICAL
  784. // Updated_previous+current interval should put us into the future, so iOverrun should be positive or zero.
  785. // If negative - the task is behind (next execution time is already in the past)
  786. unsigned long p = iCurrent->iPreviousMillis;
  787. iCurrent->iOverrun = (long) ( p + i - m );
  788. iCurrent->iStartDelay = (long) ( m - p );
  789. #endif // _TASK_TIMECRITICAL
  790. iCurrent->iDelay = i;
  791. #ifdef _TASK_OO_CALLBACKS
  792. idleRun = !iCurrent->Callback();
  793. #else
  794. if ( iCurrent->iCallback ) {
  795. iCurrent->iCallback();
  796. idleRun = false;
  797. }
  798. #endif // _TASK_OO_CALLBACKS
  799. }
  800. } while (0); //guaranteed single run - allows use of "break" to exit
  801. iCurrent = nextTask;
  802. #if defined (ARDUINO_ARCH_ESP8266) || defined (ARDUINO_ARCH_ESP32)
  803. yield();
  804. #endif // ARDUINO_ARCH_ESP8266
  805. }
  806. #ifdef _TASK_SLEEP_ON_IDLE_RUN
  807. if (idleRun && iAllowSleep) {
  808. #ifdef ARDUINO_ARCH_AVR // Could be used only for AVR-based boards.
  809. set_sleep_mode(SLEEP_MODE_IDLE);
  810. sleep_enable();
  811. /* Now enter sleep mode. */
  812. sleep_mode();
  813. /* The program will continue from here after the timer timeout ~1 ms */
  814. sleep_disable(); /* First thing to do is disable sleep. */
  815. #endif // ARDUINO_ARCH_AVR
  816. #ifdef CORE_TEENSY
  817. asm("wfi");
  818. #endif //CORE_TEENSY
  819. #ifdef ARDUINO_ARCH_ESP8266
  820. // to do: find suitable sleep function for esp8266
  821. t2 = micros() - t1;
  822. if (t2 < _TASK_ESP8266_DLY_THRESHOLD) delay(1); // ESP8266 implementation of delay() uses timers and yield
  823. #endif // ARDUINO_ARCH_ESP8266
  824. #ifdef ARDUINO_ARCH_ESP32
  825. //TODO: find a correct light sleep implementation for ESP32
  826. // esp_sleep_enable_timer_wakeup(1000); //1 ms
  827. // int ret= esp_light_sleep_start();
  828. t2 = micros() - t1;
  829. if (t2 < _TASK_ESP8266_DLY_THRESHOLD) delay(1);
  830. #endif // ARDUINO_ARCH_ESP32
  831. }
  832. #endif // _TASK_SLEEP_ON_IDLE_RUN
  833. return (idleRun);
  834. }
  835. #endif /* _TASKSCHEDULER_H_ */