Przeglądaj źródła

v2.2.1 - inline constructors, yield(), StatusRequest enabled Tasks

* inlined constructors. Added "yield()" and "yieldOnce()" functions to
easily break down and chain back together long running callback methods
* added "getCount()" to StatusRequest objects, made every task
StatusRequest enabled. Internal StatusRequest objects are accessible via
"getInternalStatusRequest()" method.
* Added Yield() example #14
Anatoli Arkhipenko 9 lat temu
rodzic
commit
72b5eb8153

+ 20 - 10
LICENSE.txt

@@ -1,17 +1,27 @@
 Copyright (c) 2015, Anatoli Arkhipenko.
 All rights reserved.
 
-Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
 
-1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+1. Redistributions of source code must retain the above copyright notice, 
+   this list of conditions and the following disclaimer.
 
-2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
 
-3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+3. Neither the name of the copyright holder nor the names of its contributors
+   may be used to endorse or promote products derived from this software without
+   specific prior written permission.
 
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 
-INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
-IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 
-OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
-OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
+OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.

+ 7 - 1
README

@@ -1,5 +1,5 @@
 Task Scheduler – cooperative multitasking for Arduino microcontrollers
-Version 2.2.0: 2016-11-17
+Version 2.2.1: 2016-12-20
 
 If you find TaskScheduler useful for your Arduino project, please drop me an email: arkhipenko@hotmail.com
 ----------------------------------------------------------------------------------------------------------
@@ -66,6 +66,12 @@ Check out what TaskScheduler can do:
 
 Changelog:
 =========
+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.2.0:
     2016-11-17 - all methods made 'inline' to support inclusion of TaskSchedule.h file into other header files
 

+ 339 - 0
examples/Scheduler_example14_Yield/Scheduler_example14_Yield.ino

@@ -0,0 +1,339 @@
+/**
+      This test illustrates the use if yield methods and internal StatusRequest objects
+      THIS TEST HAS BEEN TESTED ON NODEMCU V.2 (ESP8266)
+
+      The WiFi initialization and NTP update is executed in parallel to blinking the onboard LED
+      and an external LED connected to D2 (GPIO04)
+      Try running with and without correct WiFi parameters to observe the difference in behaviour
+*/
+
+#define _TASK_SLEEP_ON_IDLE_RUN
+#define _TASK_STATUS_REQUEST
+#include <TaskScheduler.h>
+
+#include <ESP8266WiFi.h>
+#include <WiFiUdp.h>
+
+Scheduler ts;
+
+// Callback methods prototypes
+void connectInit();
+void ledCallback();
+bool ledOnEnable();
+void ledOnDisable();
+void ledOn();
+void ledOff();
+void ntpUpdateInit();
+
+// Tasks
+
+Task  tConnect    (TASK_SECOND, TASK_FOREVER, &connectInit, &ts, true);
+Task  tLED        (TASK_IMMEDIATE, TASK_FOREVER, &ledCallback, &ts, false, &ledOnEnable, &ledOnDisable);
+
+// Tasks running on events
+Task  tNtpUpdate  (&ntpUpdateInit, &ts);
+
+// Replace with WiFi parameters of your Access Point/Router:
+const char *ssid  =  "wifi_network";
+const char *pwd   =  "wifi_password";
+
+long  ledDelayOn, ledDelayOff;
+
+#define LEDPIN            D0      // Onboard LED pin - linked to WiFi
+#define LEDPIN2           D2      // External LED
+#define CONNECT_TIMEOUT   30      // Seconds
+#define CONNECT_OK        0       // Status of successful connection to WiFi
+#define CONNECT_FAILED    (-99)   // Status of failed connection to WiFi
+
+// NTP Related Definitions
+#define NTP_PACKET_SIZE  48       // NTP time stamp is in the first 48 bytes of the message
+
+IPAddress     timeServerIP;       // time.nist.gov NTP server address
+const char*   ntpServerName = "time.nist.gov";
+byte          packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
+unsigned long epoch;
+
+WiFiUDP udp;                      // A UDP instance to let us send and receive packets over UDP
+
+#define LOCAL_NTP_PORT  2390      // Local UDP port for NTP update
+
+
+
+void setup() {
+  Serial.begin(74880);
+  Serial.println(F("TaskScheduler test #14 - Yield and internal StatusRequests"));
+  Serial.println(F("=========================================================="));
+  Serial.println();
+
+  pinMode (LEDPIN, OUTPUT);
+  pinMode (LEDPIN2, OUTPUT);
+
+  tNtpUpdate.waitFor( tConnect.getInternalStatusRequest() );  // NTP Task will start only after connection is made
+}
+
+void loop() {
+  ts.execute();                   // Only Scheduler should be executed in the loop
+}
+
+/**
+   Initiate connection to the WiFi network
+*/
+void connectInit() {
+  Serial.print(millis());
+  Serial.println(F(": connectInit."));
+  Serial.println(F("WiFi parameters: "));
+  Serial.print(F("SSID: ")); Serial.println(ssid);
+  Serial.print(F("PWD : ")); Serial.println(pwd);
+
+  WiFi.mode(WIFI_STA);
+  WiFi.hostname("esp8266");
+  WiFi.begin(ssid, pwd);
+  yield();
+
+  ledDelayOn = TASK_SECOND / 2;
+  ledDelayOff = TASK_SECOND / 4;
+  tLED.enable();
+
+  tConnect.yield(&connectCheck);            // This will pass control back to Scheduler and then continue with connection checking
+}
+
+/**
+   Periodically check if connected to WiFi
+   Re-request connection every 5 seconds
+   Stop trying after a timeout
+*/
+void connectCheck() {
+  Serial.print(millis());
+  Serial.println(F(": connectCheck."));
+
+  if (WiFi.status() == WL_CONNECTED) {                // Connection established
+    Serial.print(millis());
+    Serial.print(F(": Connected to AP. Local ip: "));
+    Serial.println(WiFi.localIP());
+    tConnect.disable();
+  }
+  else {
+
+    if (tConnect.getRunCounter() % 5 == 0) {          // re-request connection every 5 seconds
+
+      Serial.print(millis());
+      Serial.println(F(": Re-requesting connection to AP..."));
+
+      WiFi.disconnect(true);
+      yield();                                        // This is an esp8266 standard yield to allow linux wifi stack run
+      WiFi.hostname("esp8266");
+      WiFi.mode(WIFI_STA);
+      WiFi.begin(ssid, pwd);
+      yield();                                        // This is an esp8266 standard yield to allow linux wifi stack run
+    }
+
+    if (tConnect.getRunCounter() == CONNECT_TIMEOUT) {  // Connection Timeout
+      tConnect.getInternalStatusRequest()->signal(CONNECT_FAILED);  // Signal unsuccessful completion
+      tConnect.disable();
+
+      Serial.print(millis());
+      Serial.println(F(": connectOnDisable."));
+      Serial.print(millis());
+      Serial.println(F(": Unable to connect to WiFi."));
+
+      ledDelayOn = TASK_SECOND / 16;                  // Blink LEDs quickly due to error
+      ledDelayOff = TASK_SECOND / 16;
+      tLED.enable();
+    }
+  }
+}
+
+/**
+   Initiate NTP update if connection was established
+*/
+void ntpUpdateInit() {
+  Serial.print(millis());
+  Serial.println(F(": ntpUpdateInit."));
+
+  if ( tConnect.getInternalStatusRequest()->getStatus() != CONNECT_OK ) {  // Check status of the Connect Task
+    Serial.print(millis());
+    Serial.println(F(": cannot update NTP - not connected."));
+    return;
+  }
+
+  udp.begin(LOCAL_NTP_PORT);
+  if ( WiFi.hostByName(ntpServerName, timeServerIP) ) { //get a random server from the pool
+
+    Serial.print(millis());
+    Serial.print(F(": timeServerIP = "));
+    Serial.println(timeServerIP);
+
+    sendNTPpacket(timeServerIP); // send an NTP packet to a time server
+  }
+  else {
+    Serial.print(millis());
+    Serial.println(F(": NTP server address lookup failed."));
+    tLED.disable();
+    udp.stop();
+    tNtpUpdate.disable();
+    return;
+  }
+
+  ledDelayOn = TASK_SECOND / 8;
+  ledDelayOff = TASK_SECOND / 8;
+  tLED.enable();
+
+  tNtpUpdate.set( TASK_SECOND, CONNECT_TIMEOUT, &ntpCheck );
+  tNtpUpdate.enableDelayed();
+}
+
+/**
+ * Check if NTP packet was received
+ * Re-request every 5 seconds
+ * Stop trying after a timeout
+ */
+void ntpCheck() {
+  Serial.print(millis());
+  Serial.println(F(": ntpCheck."));
+
+  if ( tNtpUpdate.getRunCounter() % 5 == 0) {
+
+    Serial.print(millis());
+    Serial.println(F(": Re-requesting NTP update..."));
+
+    udp.stop();
+    yield();
+    udp.begin(LOCAL_NTP_PORT);
+    sendNTPpacket(timeServerIP);
+    return;
+  }
+
+  if ( doNtpUpdateCheck()) {
+    Serial.print(millis());
+    Serial.println(F(": NTP Update successful"));
+
+    Serial.print(millis());
+    Serial.print(F(": Unix time = "));
+    Serial.println(epoch);
+
+    tLED.disable();
+    tNtpUpdate.disable();
+    udp.stop();
+  }
+  else {
+    if ( tNtpUpdate.isLastIteration() ) {
+      Serial.print(millis());
+      Serial.println(F(": NTP Update failed"));
+      tLED.disable();
+      udp.stop();
+    }
+  }
+}
+
+/**
+ * Send NTP packet to NTP server
+ */
+unsigned long sendNTPpacket(IPAddress & address)
+{
+  Serial.print(millis());
+  Serial.println(F(": sendNTPpacket."));
+
+  // set all bytes in the buffer to 0
+  memset(packetBuffer, 0, NTP_PACKET_SIZE);
+  // Initialize values needed to form NTP request
+  // (see URL above for details on the packets)
+  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
+  packetBuffer[1] = 0;     // Stratum, or type of clock
+  packetBuffer[2] = 6;     // Polling Interval
+  packetBuffer[3] = 0xEC;  // Peer Clock Precision
+  // 8 bytes of zero for Root Delay & Root Dispersion
+  packetBuffer[12]  = 49;
+  packetBuffer[13]  = 0x4E;
+  packetBuffer[14]  = 49;
+  packetBuffer[15]  = 52;
+
+  // all NTP fields have been given values, now
+  // you can send a packet requesting a timestamp:
+  udp.beginPacket(address, 123); //NTP requests are to port 123
+  udp.write(packetBuffer, NTP_PACKET_SIZE);
+  udp.endPacket();
+  yield();
+}
+
+/**
+ * Check if a packet was recieved.
+ * Process NTP information if yes
+ */
+bool doNtpUpdateCheck() {
+
+  Serial.print(millis());
+  Serial.println(F(": doNtpUpdateCheck."));
+
+  yield();
+  int cb = udp.parsePacket();
+  if (cb) {
+    // We've received a packet, read the data from it
+    udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
+
+    //the timestamp starts at byte 40 of the received packet and is four bytes,
+    // or two words, long. First, esxtract the two words:
+
+    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
+    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
+    // combine the four bytes (two words) into a long integer
+    // this is NTP time (seconds since Jan 1 1900):
+    unsigned long secsSince1900 = highWord << 16 | lowWord;
+
+    // now convert NTP time into everyday time:
+    // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
+    const unsigned long seventyYears = 2208988800UL;
+    // subtract seventy years:
+    epoch = secsSince1900 - seventyYears;
+    return (epoch != 0);
+  }
+  return false;
+}
+
+/**
+ * Flip the LED state based on the current state
+ */
+bool ledState;
+void ledCallback() {
+  if ( ledState ) ledOff();
+  else ledOn();
+}
+
+/**
+ * Make sure the LED starts lit
+ */
+bool ledOnEnable() {
+  ledOn();
+  return true;
+}
+
+/**
+ * Make sure LED ends dimmed
+ */
+void ledOnDisable() {
+  ledOff();
+}
+
+/**
+ * Turn LEDs on. 
+ * Set appropriate delay.
+ * PLEASE NOTE: NodeMCU onbaord LED is active-low
+ */
+void ledOn() {
+  ledState = true;
+  digitalWrite(LEDPIN, LOW);
+  digitalWrite(LEDPIN2, HIGH);
+  tLED.delay( ledDelayOn );
+}
+
+/**
+ * Turn LEDs off. 
+ * Set appropriate delay.
+ * PLEASE NOTE: NodeMCU onbaord LED is active-low
+ */
+void ledOff() {
+  ledState = false;
+  digitalWrite(LEDPIN, HIGH);
+  digitalWrite(LEDPIN2, LOW);
+  tLED.delay( ledDelayOff );
+}
+

BIN
extras/TaskScheduler.doc


+ 105 - 12
extras/TaskScheduler.html

@@ -6,7 +6,7 @@
 	<meta name="generator" content="LibreOffice 4.2.8.2 (Linux)">
 	<meta name="created" content="20150206;163000000000000">
 	<meta name="changedby" content="Anatoli Arkhipenko">
-	<meta name="changed" content="20160202;231400000000000">
+	<meta name="changed" content="20161222;100000000000">
 	<style type="text/css">
 	<!--
 		@page { margin: 0.79in }
@@ -24,7 +24,7 @@ Scheduler</b></font></p>
 <p class="western" style="margin-bottom: 0in"><b>Cooperative
 multitasking for Arduino microcontrollers</b></p>
 <p class="western" style="margin-bottom: 0in; border-top: none; border-bottom: 1px solid #000000; border-left: none; border-right: none; padding-top: 0in; padding-bottom: 0.01in; padding-left: 0in; padding-right: 0in">
-<font size="2" style="font-size: 11pt"><b>Version 2.1.0: 2016-02-02</b></font></p>
+<font size="2" style="font-size: 11pt"><b>Version 2.2.1: 2016-12-20</b></font></p>
 <p class="western" style="margin-bottom: 0in"><br>
 </p>
 <p class="western" style="margin-bottom: 0in"><b>OVERVIEW</b>:</p>
@@ -63,16 +63,17 @@ between 15 and 18 microseconds per scheduling pass (Arduino UNO rev 3
 <p class="western" style="margin-bottom: 0in"><br>
 </p>
 <p class="western" style="margin-bottom: 0in"><b>TASK</b>:</p>
-<p class="western" style="margin-bottom: 0in">“Task” is a
-container concept that links together:</p>
+<p class="western" style="margin-bottom: 0in">“Task” is an
+action, a part of the program logic, which requires scheduled
+execution. A concept of Task combines the following aspects:</p>
 <ol>
 	<li><p class="western" style="margin-bottom: 0in">Program code
-	performing specific task activities (callback methods)</p>
+	performing specific activities (callback methods)</p>
 	<li><p class="western" style="margin-bottom: 0in">Execution interval</p>
 	<li><p class="western" style="margin-bottom: 0in">Number of
 	execution iterations</p>
 	<li><p class="western" style="margin-bottom: 0in">(Optionally)
-	Execution event (Status Request)</p>
+	Execution start event (Status Request)</p>
 	<li><p class="western" style="margin-bottom: 0in">(Optionally)
 	Pointer to a Local Task Storage area</p>
 </ol>
@@ -273,6 +274,11 @@ TaskScheduler with support for StatusRequest object. Status Requests
 are objects allowing tasks to wait on an event, and signal event
 completion to each other. 
 </p>
+<p class="western" style="margin-bottom: 0in"><b>NOTE: </b> tas of
+version 2.2.1 each task has internal StatusRequest object, which
+triggered active at the moment Task is enabled, and triggered
+complete at the moment the task is disabled. These events could be
+used by other Tasks for event-driven execution</p>
 <p class="western" style="margin-bottom: 0in"><br>
 </p>
 <p class="western" style="margin-bottom: 0in">#define <b>_TASK_WDT_IDS</b></p>
@@ -1335,6 +1341,16 @@ the Task against <b>OnEnable</b> infinite loop).
 </p>
 <p class="western" style="margin-left: 0.49in; margin-bottom: 0in"><br>
 </p>
+<p class="western" style="margin-left: 0.49in; margin-bottom: 0in"><b>NOTE:
+</b>internal StatusRequest object will be set waiting for an event
+when Task is enabled (if TaskScheduler is compiled with support for
+StatusRequests). StatusRequest object is set waiting <b>after </b>the
+call to onEnable() method of the Task (if defined). Consequently, any
+Task#2 that is expected to wait on this Task’s internal
+StatusRequest should do it only <b>after </b>this task is enabled. 
+</p>
+<p class="western" style="margin-left: 0.49in; margin-bottom: 0in"><br>
+</p>
 <p class="western" style="margin-bottom: 0in; page-break-after: avoid">
 <b>bool enableIfNot();</b></p>
 <p class="western" style="margin-bottom: 0in; page-break-after: avoid">
@@ -1347,6 +1363,8 @@ task was disabled. Since <b>enable() </b>schedules Task for execution
 immediately, this method provides a way to activate tasks and
 schedule them for immediate execution only if they are not active
 already.</p>
+<p class="western" style="margin-left: 0.49in; margin-bottom: 0in">All
+<b>NOTES</b> from the enable() method apply.</p>
 <p class="western" style="margin-bottom: 0in"><br>
 </p>
 <p class="western" style="margin-bottom: 0in"><b>void delay();</b></p>
@@ -1440,6 +1458,19 @@ Calling <b>disable</b> 3 times for instance will invoke <b>OnDisable</b>
 only once.</p>
 <p class="western" style="margin-left: 0.49in; margin-bottom: 0in"><br>
 </p>
+<p class="western" style="margin-left: 0.49in; margin-bottom: 0in"><b>NOTE:
+</b>internal StatusRequest object will signal completion of an event
+when Task is disabled (if TaskScheduler is compiled with support for
+StatusRequests). StatusRequest object is set complete <b>after </b>the
+call to onDisable() method of the Task (if defined). Consequently,
+the task which has to signal its completion to other Tasks could not
+restart itself. Do so will not ever set the internal StatusRequest
+object to a complete status, since the Task is never really disabled.
+</p>
+<p class="western" style="margin-left: 0.49in; margin-bottom: 0in"><br>
+</p>
+<p class="western" style="margin-left: 0.49in; margin-bottom: 0in"><br>
+</p>
 <p class="western" style="margin-bottom: 0in"><b>void set(unsigned
 long aInterval, long aIterations, void (*aCallback)() , bool
 (*aOnEnable)() , void (*aOnDisable)());</b></p>
@@ -1497,8 +1528,37 @@ interval. If immediate invocation is required, call
 </p>
 <p class="western" style="margin-bottom: 0in"><br>
 </p>
-<p class="western" style="margin-bottom: 0in"><b>STATUS REQUEST
-METHODS:</b></p>
+<p class="western" style="margin-bottom: 0in"><b>void yield(void
+(*aCallback)())</b></p>
+<p class="western" style="margin-bottom: 0in"><br>
+</p>
+<p class="western" style="margin-bottom: 0in">This method could be
+used to break up long callback methods. A long callback method should
+be broken up into several shorter methods. Yield method just gives
+control back to scheduler, while ensuring that next iteration of the
+Task is executed immediately with the next callback method. Basically
+“yield(&amp;callBack2)” is equivalent to setting new callback
+method, and forcing next iteration to be immediate. Please not that
+original interval and number of iterations are preserved. Even the
+runcounter of the callback2 after yielding will remain the same.
+Typically a call to yield() method is the last line of the method
+yielding.</p>
+<p class="western" style="margin-bottom: 0in"><br>
+</p>
+<p class="western" style="margin-bottom: 0in"><b>void yield</b><font face="Times New Roman, serif"><b>Once</b></font><b>(void
+(*aCallback)())</b></p>
+<p class="western" style="margin-bottom: 0in"><br>
+</p>
+<p class="western" style="margin-bottom: 0in">This method is
+equivalent to <b>yield(),</b> only execution of the target <i><b>a</b></i><font face="Times New Roman, serif"><span lang="ru-RU"><i><b>С</b></i></span></font><i><b>allback</b></i>
+method is set to happen only once, after which the Task will be
+disabled.</p>
+<p class="western" style="margin-bottom: 0in"><br>
+</p>
+<p class="western" style="margin-bottom: 0in"><br>
+</p>
+<p class="western" style="margin-bottom: 0in; page-break-after: avoid">
+<b>STATUS REQUEST METHODS:</b></p>
 <p class="western" style="margin-bottom: 0in; page-break-after: avoid">
 <br>
 </p>
@@ -1558,11 +1618,34 @@ sequence of events to use Status Request object is as follows:</p>
 <p class="western" style="margin-bottom: 0in"><b>StatusRequest*
 getStatusRequest()</b></p>
 <p class="western" style="margin-left: 0.49in; margin-bottom: 0in">Returns
-a StatusReqeust object this Task was waiting on. 
+a pointer to StatusReqeust object this Task was waiting on. 
 </p>
 <p class="western" style="margin-left: 0.49in; margin-bottom: 0in"><br>
 </p>
-<p class="western" style="margin-left: 0.49in; margin-bottom: 0in"><br>
+<p class="western" style="margin-bottom: 0in"><b>StatusRequest*
+getInternalStatusRequest()</b></p>
+<p class="western" style="margin-left: 0.49in; margin-bottom: 0in">Returns
+a pointer to an internal StatusReqeust object associated with this
+Task. Internal StatusRequest object is:</p>
+<ol>
+	<li><p class="western" style="margin-bottom: 0in">Always waits on 1
+	event – completion of this task</p>
+	<li><p class="western" style="margin-bottom: 0in">Is activated (set
+	to “waiting” status) after Task is enabled</p>
+	<li><p class="western" style="margin-bottom: 0in">Is completed after
+	Task is disabled (either explicitly, or by running out of
+	iterations)</p>
+</ol>
+<p class="western" style="margin-bottom: 0in"><br>
+</p>
+<p class="western" style="margin-left: 0.49in; margin-bottom: 0in"><b>NOTE:
+</b>Please remember that a task is deactivated at the next scheduling
+pass after the last iteration, which means that other Tasks in the
+chain will have a chance to run before Task StatusRequest signaling
+completion of the internal StatusRequest. However, there is no
+further delay – deactivation will take place at the next scheduling
+pass.</p>
+<p class="western" style="margin-bottom: 0in"><br>
 </p>
 <p class="western" style="margin-left: 0.49in; margin-bottom: 0in"><br>
 </p>
@@ -1724,7 +1807,7 @@ happen.</p>
 <p class="western" style="margin-bottom: 0in"><b>bool completed () </b>
 </p>
 <p class="western" style="margin-left: 0.49in; margin-bottom: 0in">Returns
-<b>true</b> if status has completed.</p>
+<b>true</b> if status request event has completed.</p>
 <p class="western" style="margin-left: 0.49in; margin-bottom: 0in"><br>
 </p>
 <p class="western" style="margin-bottom: 0in"><b>int getStatus()</b></p>
@@ -1741,6 +1824,16 @@ the status code passed to the status request object by the <b>signal()
 completion of a request.</p>
 <p class="western" style="margin-bottom: 0in"><br>
 </p>
+<p class="western" style="margin-bottom: 0in"><b>int getCount()</b></p>
+<p class="western" style="margin-left: 0.49in; margin-bottom: 0in">Returns
+the count of events not yet completed. Typically by default a
+StatusRequest object only waits on 1 event. However, in the
+situations where a StatusRequest object is waiting on multiple
+events, a number of events <b>not yet completed </b>is returned by
+this method.  
+</p>
+<p class="western" style="margin-bottom: 0in"><br>
+</p>
 <p class="western" style="margin-bottom: 0in"><br>
 </p>
 <p class="western" style="margin-bottom: 0in; page-break-after: avoid">
@@ -3198,7 +3291,7 @@ time examples of TaskScheduler are available here:</font></font></p>
 <p class="western" style="margin-bottom: 0in"><br>
 </p>
 <div title="footer">
-	<p style="margin-top: 0.35in; margin-bottom: 0in">	<sdfield type=PAGE subtype=RANDOM format=ARABIC>33</sdfield></p>
+	<p style="margin-top: 0.35in; margin-bottom: 0in">	<sdfield type=PAGE subtype=RANDOM format=ARABIC>34</sdfield></p>
 </div>
 </body>
 </html>

+ 5 - 0
keywords.txt

@@ -43,6 +43,10 @@ setCallback	KEYWORD2
 setOnEnable	KEYWORD2
 setOnDisable	KEYWORD2
 disableOnLastIteration	KEYWORD2
+yield	KEYWORD2
+yieldOnce	KEYWORD2
+getInternalStatusRequest	KEYWORD2
+getCount	KEYWORD2
 getOverrun	KEYWORD2
 getStartDelay	KEYWORD2
 isFirstIteration	KEYWORD2
@@ -56,6 +60,7 @@ getStatus	KEYWORD2
 waitFor	KEYWORD2
 waitForDelayed	KEYWORD2
 getStatusRequest	KEYWORD2
+getCount	KEYWORD2
 setId	KEYWORD2
 getId	KEYWORD2
 setControlPoint	KEYWORD2

+ 1 - 1
library.properties

@@ -1,5 +1,5 @@
 name=TaskScheduler
-version=2.2.0
+version=2.2.1
 author=Anatoli Arkhipenko <arkhipenko@hotmail.com>
 maintainer=Anatoli Arkhipenko <arkhipenko@hotmail.com>
 sentence=A light-weight cooperative multitasking library for arduino microcontrollers.

+ 70 - 18
src/TaskScheduler.h

@@ -1,5 +1,5 @@
 // Cooperative multitasking library for Arduino
-// Copyright (c) 2015 Anatoli Arkhipenko
+// Copyright (c) 2015, 2016 Anatoli Arkhipenko
 //
 // Changelog:
 // v1.0.0:
@@ -14,7 +14,8 @@
 //
 // 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)
@@ -33,10 +34,13 @@
 //
 // 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 - 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-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
@@ -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:
@@ -90,6 +97,12 @@
 //
 // 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. 
 
 #include <Arduino.h>
 
@@ -145,15 +158,15 @@ extern "C" {
 
 #ifndef _TASK_MICRO_RES
 
-#define TASK_SECOND			1000L
-#define TASK_MINUTE		   60000L
-#define TASK_HOUR		 3600000L
+#define TASK_SECOND			1000UL
+#define TASK_MINUTE		   60000UL
+#define TASK_HOUR		 3600000UL
 
 #else
 
-#define TASK_SECOND		1000000L
-#define TASK_MINUTE	   60000000L
-#define TASK_HOUR	 3600000000L
+#define TASK_SECOND		1000000UL
+#define TASK_MINUTE	   60000000UL
+#define TASK_HOUR	 3600000000UL
 
 #endif  // _TASK_MICRO_RES
 
@@ -165,13 +178,14 @@ extern "C" {
 
 class StatusRequest {
 	public:
-		StatusRequest() {iCount = 0; iStatus = 0; }
+		inline StatusRequest() {iCount = 0; iStatus = 0; }
 		inline void setWaiting(unsigned int aCount = 1) { iCount = aCount; iStatus = 0; }
 		inline bool signal(int aStatus = 0);
 		inline void signalComplete(int aStatus = 0);
 		inline bool pending() { return (iCount != 0); }
 		inline bool completed() { return (iCount == 0); }
 		inline int getStatus() { return iStatus; }
+		inline int getCount() { return iCount; }
 		
 	private:
 		unsigned int	iCount;  					// number of statuses to wait for. waiting for more that 65000 events seems unreasonable: unsigned int should be sufficient
@@ -198,9 +212,9 @@ class Scheduler;
 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);
+		inline 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);
+		inline Task(void (*aCallback)()=NULL, Scheduler* aScheduler=NULL, bool (*aOnEnable)()=NULL, void (*aOnDisable)()=NULL);
 #endif  // _TASK_STATUS_REQUEST
 
 		inline void enable();
@@ -221,6 +235,8 @@ class Task {
 		inline void setCallback(void (*aCallback)()) { iCallback = aCallback; }
 		inline void setOnEnable(bool (*aCallback)()) { iOnEnable = aCallback; }
 		inline void setOnDisable(void (*aCallback)()) { iOnDisable = aCallback; }
+		inline void yield(void (*aCallback)());
+		inline void yieldOnce(void (*aCallback)());
 #ifdef _TASK_TIMECRITICAL
 		inline long getOverrun() { return iOverrun; }
 		inline long getStartDelay() { return iStartDelay; }
@@ -231,6 +247,7 @@ class Task {
 		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() {return iStatusRequest; }
+		inline StatusRequest* getInternalStatusRequest() {return &iMyStatusRequest; }
 #endif  // _TASK_STATUS_REQUEST
 #ifdef _TASK_WDT_IDS
 		inline void setId(unsigned int aID) { iTaskID = aID; }
@@ -264,6 +281,7 @@ class Task {
 		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)
@@ -282,7 +300,7 @@ class Task {
 class Scheduler {
 	friend class Task;
 	public:
-		Scheduler();
+		inline Scheduler();
 		inline void init();
 		inline void addTask(Task& aTask);
 		inline void deleteTask(Task& aTask);
@@ -418,6 +436,7 @@ void Task::reset() {
 #endif  // _TASK_LTS_POINTER
 #ifdef _TASK_STATUS_REQUEST
 	iStatus.waiting = 0;
+	iMyStatusRequest.signalComplete();
 #endif  // _TASK_STATUS_REQUEST
 }
 
@@ -444,6 +463,28 @@ void Task::setIterations(long aIterations) {
 	iSetIterations = iIterations = aIterations; 
 }
 
+/** 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 (void (*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++;
+}
+
+/** 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 (void (*aCallback)()) {
+	yield(aCallback);
+	iIterations = 1;
+}
+
 /** Enables the task 
  *  schedules it for execution as soon as possible,
  *  and resets the RunCounter back to zero
@@ -463,6 +504,11 @@ void Task::enable() {
 			iStatus.enabled = true;
 		}
 		iPreviousMillis = _TASK_TIME_FUNCTION() - (iDelay = iInterval);
+#ifdef _TASK_STATUS_REQUEST
+		if ( iStatus.enabled ) {
+			iMyStatusRequest.setWaiting();
+		}
+#endif
 	}
 }
 
@@ -525,6 +571,9 @@ bool Task::disable() {
 		(*iOnDisable)();
 		iScheduler->iCurrent = current;
 	}
+#ifdef _TASK_STATUS_REQUEST
+	iMyStatusRequest.signalComplete();
+#endif
 	return (previousEnabled);
 }
 
@@ -771,6 +820,9 @@ bool Scheduler::execute() {
 			}
 		} while (0); 	//guaranteed single run - allows use of "break" to exit 
 		iCurrent = iCurrent->iNext;
+#ifdef ARDUINO_ARCH_ESP8266
+	    yield();
+#endif  // ARDUINO_ARCH_ESP8266
 	}
 
 #ifdef _TASK_SLEEP_ON_IDLE_RUN