Browse Source

v4.0.0 - major rework of thread safety for _TASK_THREAD_SAFE

Anatoli Arkhipenko 1 năm trước cách đây
mục cha
commit
f404e97028

+ 1 - 1
LICENSE.txt

@@ -1,4 +1,4 @@
-Copyright (c) 2015-2020, Anatoli Arkhipenko.
+Copyright (c) 2015-2025, Anatoli Arkhipenko.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without modification,

+ 4 - 2
README.md

@@ -1,6 +1,6 @@
 # Task Scheduler
 ### Cooperative multitasking for Arduino, ESPx, STM32 and other microcontrollers
-#### Version 3.9.0: 2024-08-14 [Latest updates](https://github.com/arkhipenko/TaskScheduler/wiki/Latest-Updates)
+#### Version 4.0.0: 2024-10-26 [Latest updates](https://github.com/arkhipenko/TaskScheduler/wiki/Latest-Updates)
 
 [![arduino-library-badge](https://www.ardu-badge.com/badge/TaskScheduler.svg?)](https://www.ardu-badge.com/TaskScheduler)[![xscode](https://img.shields.io/badge/Available%20on-xs%3Acode-blue?style=?style=plastic&logo=appveyor&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRF////////VXz1bAAAAAJ0Uk5T/wDltzBKAAAAlUlEQVR42uzXSwqAMAwE0Mn9L+3Ggtgkk35QwcnSJo9S+yGwM9DCooCbgn4YrJ4CIPUcQF7/XSBbx2TEz4sAZ2q1RAECBAiYBlCtvwN+KiYAlG7UDGj59MViT9hOwEqAhYCtAsUZvL6I6W8c2wcbd+LIWSCHSTeSAAECngN4xxIDSK9f4B9t377Wd7H5Nt7/Xz8eAgwAvesLRjYYPuUAAAAASUVORK5CYII=)](https://xscode.com/arkhipenko/TaskScheduler)
 
@@ -37,6 +37,8 @@ _“Everybody who learns concurrency and thinks they understand it, ends up find
 16. Thread-safe scheduling while running under preemptive scheduler (i. e., FreeRTOS)
 17. Optional self-destruction of dynamically created tasks upon disable
 18. Support for "tickless" execution under FreeRTOS (continous sleep until next scheduled task invocation)
+19. Support for both Arduino IDE stype (headers only) and PlatformIO style (Header + CPP files)
+20. Support for non-Arduino platforms
 
 Scheduling overhead: between `15` and `18` microseconds per scheduling pass (Arduino UNO rev 3 @ `16MHz` clock, single scheduler w/o prioritization)
 
@@ -52,7 +54,7 @@ Scheduling overhead: between `15` and `18` microseconds per scheduling pass (Ard
 * nRF52 Adafruit Core (tested on nRF52840 with v3.6.2 workround)
 * STM32 (tested on Mini USB STM32F103RCBT6 ARM Cortex-M3 leaflabs Leaf maple mini module F)
 * MSP430 and MSP432 boards
-* Raspberry Pi (requires external `Arduino.h` and `millis()` implementation)
+* Raspberry Pi (requires external `_TASK_NON_ARDUINO` and `_task_millis()` implementation)
 
 
 

+ 0 - 2
examples/Scheduler_example00_Blink/Scheduler_example00_Blink.ino

@@ -28,8 +28,6 @@
 // #define _TASK_OO_CALLBACKS       // Support for callbacks via inheritance
 // #define _TASK_EXPOSE_CHAIN       // Methods to access tasks in the task chain
 // #define _TASK_SCHEDULING_OPTIONS // Support for multiple scheduling options
-// #define _TASK_DEFINE_MILLIS      // Force forward declaration of millis() and micros() "C" style
-// #define _TASK_EXTERNAL_TIME      // Custom millis() and micros() methods
 // #define _TASK_THREAD_SAFE        // Enable additional checking for thread safety
 // #define _TASK_SELF_DESTRUCT      // Enable tasks to "self-destruct" after disable
 #include <TaskScheduler.h>

+ 0 - 2
examples/Scheduler_example00_Blink_Namespace/Scheduler_example00_Blink_Namespace.ino

@@ -26,8 +26,6 @@
 // #define _TASK_OO_CALLBACKS       // Support for callbacks via inheritance
 // #define _TASK_EXPOSE_CHAIN       // Methods to access tasks in the task chain
 // #define _TASK_SCHEDULING_OPTIONS // Support for multiple scheduling options
-// #define _TASK_DEFINE_MILLIS      // Force forward declaration of millis() and micros() "C" style
-// #define _TASK_EXTERNAL_TIME      // Custom millis() and micros() methods
 // #define _TASK_THREAD_SAFE        // Enable additional checking for thread safety
 // #define _TASK_SELF_DESTRUCT      // Enable tasks to "self-destruct" after disable
 

+ 3 - 0
examples/Scheduler_example27_PlatformIO/.vscode/extensions.json

@@ -3,5 +3,8 @@
     // for the documentation about the extensions.json format
     "recommendations": [
         "platformio.platformio-ide"
+    ],
+    "unwantedRecommendations": [
+        "ms-vscode.cpptools-extension-pack"
     ]
 }

+ 3 - 4
examples/Scheduler_example27_PlatformIO/platformio.ini

@@ -15,7 +15,7 @@ framework = arduino
 
 [env]
 lib_deps =
-    arkhipenko/TaskScheduler @ ^3.4.0
+    arkhipenko/TaskScheduler @ ^4.0.0
     
 build_flags = 
     ; -D _TASK_TIMECRITICAL       
@@ -31,8 +31,7 @@ build_flags =
     ; -D _TASK_TIMEOUT            
     ; -D _TASK_OO_CALLBACKS       
     ; -D _TASK_EXPOSE_CHAIN       
-    ; -D _TASK_SCHEDULING_OPTIONS 
-    ; -D _TASK_DEFINE_MILLIS      
-    ; -D _TASK_EXTERNAL_TIME    
+    ; -D _TASK_SCHEDULING_OPTIONS  
+    -D _TASK_HEADER_AND_CPP
     -D _DEBUG_
     ; -D _TEST_  

+ 0 - 1
examples/Scheduler_example27_PlatformIO/src/TaskScheduler.cpp

@@ -1 +0,0 @@
-#include <TaskScheduler.h>

+ 5 - 0
examples/Scheduler_example29_NonArduino/.gitignore

@@ -0,0 +1,5 @@
+.pio
+.vscode/.browse.c_cpp.db*
+.vscode/c_cpp_properties.json
+.vscode/launch.json
+.vscode/ipch

+ 10 - 0
examples/Scheduler_example29_NonArduino/.vscode/extensions.json

@@ -0,0 +1,10 @@
+{
+    // See http://go.microsoft.com/fwlink/?LinkId=827846
+    // for the documentation about the extensions.json format
+    "recommendations": [
+        "platformio.platformio-ide"
+    ],
+    "unwantedRecommendations": [
+        "ms-vscode.cpptools-extension-pack"
+    ]
+}

+ 39 - 0
examples/Scheduler_example29_NonArduino/include/README

@@ -0,0 +1,39 @@
+
+This directory is intended for project header files.
+
+A header file is a file containing C declarations and macro definitions
+to be shared between several project source files. You request the use of a
+header file in your project source file (C, C++, etc) located in `src` folder
+by including it, with the C preprocessing directive `#include'.
+
+```src/main.c
+
+#include "header.h"
+
+int main (void)
+{
+ ...
+}
+```
+
+Including a header file produces the same results as copying the header file
+into each source file that needs it. Such copying would be time-consuming
+and error-prone. With a header file, the related declarations appear
+in only one place. If they need to be changed, they can be changed in one
+place, and programs that include the header file will automatically use the
+new version when next recompiled. The header file eliminates the labor of
+finding and changing all the copies as well as the risk that a failure to
+find one copy will result in inconsistencies within a program.
+
+In C, the usual convention is to give header files names that end with `.h'.
+It is most portable to use only letters, digits, dashes, and underscores in
+header file names, and at most one dot.
+
+Read more about using header files in official GCC documentation:
+
+* Include Syntax
+* Include Operation
+* Once-Only Headers
+* Computed Includes
+
+https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

+ 12 - 0
examples/Scheduler_example29_NonArduino/include/led.h

@@ -0,0 +1,12 @@
+#ifndef _TS27_LED_H
+#define _TS27_LED_H
+
+// LED_BUILTIN  13
+#if defined( ARDUINO_ARCH_ESP32 )
+#define LED_BUILTIN  23 // esp32 dev2 kit does not have LED
+#endif
+
+void LEDOff();
+void LEDOn();
+
+#endif //   _TS27_LED_H

+ 27 - 0
examples/Scheduler_example29_NonArduino/include/main.h

@@ -0,0 +1,27 @@
+#ifndef _TS27_MAIN_H
+#define _TS27_MAIN_H
+
+#include <Arduino.h>
+
+#ifdef _DEBUG_
+#define _PP(a) Serial.print(a);
+#define _PL(a) Serial.println(a);
+#else
+#define _PP(a)
+#define _PL(a)
+#endif
+
+#define PERIOD1 500
+#define DURATION 10000
+
+#define PERIOD2 400
+
+#define PERIOD3 300
+
+#define PERIOD4 200
+
+#define PERIOD5 600
+
+#define PERIOD6 300
+
+#endif

+ 46 - 0
examples/Scheduler_example29_NonArduino/lib/README

@@ -0,0 +1,46 @@
+
+This directory is intended for project specific (private) libraries.
+PlatformIO will compile them to static libraries and link into executable file.
+
+The source code of each library should be placed in a an own separate directory
+("lib/your_library_name/[here are source files]").
+
+For example, see a structure of the following two libraries `Foo` and `Bar`:
+
+|--lib
+|  |
+|  |--Bar
+|  |  |--docs
+|  |  |--examples
+|  |  |--src
+|  |     |- Bar.c
+|  |     |- Bar.h
+|  |  |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
+|  |
+|  |--Foo
+|  |  |- Foo.c
+|  |  |- Foo.h
+|  |
+|  |- README --> THIS FILE
+|
+|- platformio.ini
+|--src
+   |- main.c
+
+and a contents of `src/main.c`:
+```
+#include <Foo.h>
+#include <Bar.h>
+
+int main (void)
+{
+  ...
+}
+
+```
+
+PlatformIO Library Dependency Finder will find automatically dependent
+libraries scanning project source files.
+
+More information about PlatformIO Library Dependency Finder
+- https://docs.platformio.org/page/librarymanager/ldf.html

+ 38 - 0
examples/Scheduler_example29_NonArduino/platformio.ini

@@ -0,0 +1,38 @@
+; PlatformIO Project Configuration File
+;
+;   Build options: build flags, source filter
+;   Upload options: custom upload port, speed and extra flags
+;   Library options: dependencies, extra library storages
+;   Advanced options: extra scripting
+;
+; Please visit documentation for the other options and examples
+; https://docs.platformio.org/page/projectconf.html
+
+[env:esp32dev]
+platform = espressif32
+board = esp32dev
+framework = arduino
+
+[env]
+lib_deps =
+    arkhipenko/TaskScheduler @ ^4.0.0
+    
+build_flags = 
+    ; -D _TASK_TIMECRITICAL       
+    -D _TASK_SLEEP_ON_IDLE_RUN  
+    -D _TASK_STATUS_REQUEST     
+    ; -D _TASK_WDT_IDS            
+    ; -D _TASK_LTS_POINTER        
+    ; -D _TASK_PRIORITY           
+    ; -D _TASK_MICRO_RES          
+    ; -D _TASK_STD_FUNCTION       
+    ; -D _TASK_DEBUG              
+    ; -D _TASK_INLINE             
+    ; -D _TASK_TIMEOUT            
+    ; -D _TASK_OO_CALLBACKS       
+    ; -D _TASK_EXPOSE_CHAIN       
+    ; -D _TASK_SCHEDULING_OPTIONS  
+    -D _TASK_HEADER_AND_CPP
+    -D _TASK_NON_ARDUINO
+    -D _DEBUG_
+    ; -D _TEST_  

+ 11 - 0
examples/Scheduler_example29_NonArduino/src/led.cpp

@@ -0,0 +1,11 @@
+#include <Arduino.h>
+#include "led.h"
+#include "main.h"
+
+void LEDOn() {
+  digitalWrite( LED_BUILTIN, HIGH );
+}
+
+void LEDOff() {
+  digitalWrite( LED_BUILTIN, LOW );
+}

+ 268 - 0
examples/Scheduler_example29_NonArduino/src/main.cpp

@@ -0,0 +1,268 @@
+/*
+   Every example set must have a LED blink example
+   For this one the idea is to have as many ways to blink the LED
+   as I can think of. So, here we go.
+
+   Tested on:
+   - ESP32
+
+   This example is compiled without Arduino.h in TS header. 
+*/
+
+#include <TaskSchedulerDeclarations.h>
+
+#include <Arduino.h>
+#include "main.h"
+#include "led.h"
+
+// Scheduler
+Scheduler ts;
+
+
+// Comment the following 3 functions out to observer errors due to missing Arduino.h
+unsigned long _task_millis() {
+  return xTaskGetTickCount();
+}
+
+unsigned long _task_micros() {
+  return (unsigned long) (esp_timer_get_time());
+}
+
+void _task_yield() {
+  portYIELD();
+}
+
+/*
+   Approach 1: LED is driven by the boolean variable; false = OFF, true = ON
+*/
+void blink1CB();
+Task tBlink1 ( PERIOD1 * TASK_MILLISECOND, DURATION / PERIOD1, &blink1CB, &ts, true );
+
+/*
+   Approach 2: two callback methods: one turns ON, another turns OFF
+*/
+void blink2CB_ON();
+void blink2CB_OFF();
+Task tBlink2 ( PERIOD2 * TASK_MILLISECOND, DURATION / PERIOD2, &blink2CB_ON, &ts, false );
+
+/*
+   Approach 3: Use RunCounter
+*/
+void blink3CB();
+Task tBlink3 (PERIOD3 * TASK_MILLISECOND, DURATION / PERIOD3, &blink3CB, &ts, false);
+
+/*
+   Approach 4: Use status request objects to pass control from one task to the other
+*/
+bool blink41OE();
+void blink41();
+void blink42();
+void blink42OD();
+Task tBlink4On  ( PERIOD4 * TASK_MILLISECOND, TASK_ONCE, blink41, &ts, false, &blink41OE );
+Task tBlink4Off ( PERIOD4 * TASK_MILLISECOND, TASK_ONCE, blink42, &ts, false, NULL, &blink42OD );
+
+
+/*
+   Approach 5: Two interleaving tasks
+*/
+bool blink51OE();
+void blink51();
+void blink52();
+void blink52OD();
+Task tBlink5On  ( 600 * TASK_MILLISECOND, DURATION / PERIOD5, &blink51, &ts, false, &blink51OE );
+Task tBlink5Off ( 600 * TASK_MILLISECOND, DURATION / PERIOD5, &blink52, &ts, false, NULL, &blink52OD );
+
+
+/*
+   Approach 6: RunCounter-based with random intervals
+*/
+void blink6CB();
+bool blink6OE();
+void blink6OD();
+Task tBlink6 ( PERIOD6 * TASK_MILLISECOND, DURATION / PERIOD6, &blink6CB, &ts, false, &blink6OE, &blink6OD );
+
+void setup() {
+  // put your setup code here, to run once:
+#if defined(_DEBUG_) || defined(_TEST_)
+  Serial.begin(115200);
+  delay(TASK_SECOND);
+  _PL("TaskScheduler Blink example");
+  _PL("Blinking for 10 seconds using various techniques\n");
+  delay(2 * TASK_SECOND);
+#endif
+  pinMode(LED_BUILTIN, OUTPUT);
+}
+
+void loop() {
+  ts.execute();
+}
+
+// === 1 =======================================
+bool LED_state = false;
+void blink1CB() {
+  if ( tBlink1.isFirstIteration() ) {
+    _PP(millis());
+    _PL(": Blink1 - simple flag driven");
+    LED_state = false;
+  }
+
+  if ( LED_state ) {
+    LEDOff();
+    LED_state = false;
+  }
+  else {
+    LEDOn();
+    LED_state = true;
+  }
+
+  if ( tBlink1.isLastIteration() ) {
+    tBlink2.restartDelayed( 2 * TASK_SECOND );
+    LEDOff();
+  }
+}
+
+
+// === 2 ======================================
+void blink2CB_ON() {
+  if ( tBlink2.isFirstIteration() ) {
+    _PP(millis());
+    _PL(": Blink2 - 2 callback methods");
+  }
+
+  LEDOn();
+  tBlink2.setCallback( &blink2CB_OFF );
+
+  if ( tBlink2.isLastIteration() ) {
+    tBlink3.restartDelayed( 2 * TASK_SECOND );
+    LEDOff();
+  }
+}
+
+
+void blink2CB_OFF() {
+
+  LEDOff();
+  tBlink2.setCallback( &blink2CB_ON );
+
+  if ( tBlink2.isLastIteration() ) {
+    tBlink3.restartDelayed( 2 * TASK_SECOND );
+    LEDOff();
+  }
+}
+
+
+// === 3 =====================================
+void blink3CB() {
+  if ( tBlink3.isFirstIteration() ) {
+    _PP(millis());
+    _PL(": Blink3 - Run Counter driven");
+  }
+
+  if ( tBlink3.getRunCounter() & 1 ) {
+    LEDOn();
+  }
+  else {
+    LEDOff();
+  }
+
+  if ( tBlink3.isLastIteration() ) {
+    tBlink4On.setOnEnable( &blink41OE );
+    tBlink4On.restartDelayed( 2 * TASK_SECOND );
+    LEDOff();
+  }
+}
+
+
+// === 4 =============================================
+int counter = 0;
+bool blink41OE() {
+  _PP(millis());
+  _PL(": Blink4 - Internal status request based");
+  counter = 0;
+  tBlink4On.setOnEnable( NULL );
+  return true;
+}
+
+void blink41() {
+  //  _PP(millis());
+  //  _PL(": blink41");
+  LEDOn();
+  StatusRequest* r = tBlink4On.getInternalStatusRequest();
+  tBlink4Off.waitForDelayed( r );
+  counter++;
+}
+
+void blink42() {
+  //  _PP(millis());
+  //  _PL(": blink42");
+  LEDOff();
+  StatusRequest* r = tBlink4Off.getInternalStatusRequest();
+  tBlink4On.waitForDelayed( r );
+  counter++;
+}
+
+
+void blink42OD() {
+  if ( counter >= DURATION / PERIOD4 ) {
+    tBlink4On.disable();
+    tBlink4Off.disable();
+
+    tBlink5On.setOnEnable( &blink51OE );
+    tBlink5On.restartDelayed( 2 * TASK_SECOND );
+    tBlink5Off.restartDelayed( 2 * TASK_SECOND + PERIOD5 / 2 );
+    LEDOff();
+  }
+}
+
+
+// === 5 ==========================================
+bool blink51OE() {
+  _PP(millis());
+  _PL(": Blink5 - Two interleaving tasks");
+  tBlink5On.setOnEnable( NULL );
+  return true;
+}
+void blink51() {
+  //  _PP(millis());
+  //  _PL(": blink51");
+  LEDOn();
+}
+void blink52() {
+  //  _PP(millis());
+  //  _PL(": blink52");
+  LEDOff();
+}
+void blink52OD() {
+  tBlink6.restartDelayed( 2 * TASK_SECOND );
+  LEDOff();
+}
+
+
+// === 6 ============================================
+long interval6 = 0;
+bool blink6OE() {
+  _PP(millis());
+  _PP(": Blink6 - RunCounter + Random ON interval = ");
+  interval6 = random( 100, 901 );
+  tBlink6.setInterval( interval6 );
+  _PL( interval6 );
+  tBlink6.delay( 2 * TASK_SECOND );
+
+  return true;
+}
+
+void blink6CB() {
+  if ( tBlink6.getRunCounter() & 1 ) {
+    LEDOn();
+    tBlink6.setInterval( interval6 );
+  }
+  else {
+    LEDOff();
+    tBlink6.setInterval( TASK_SECOND - interval6 );
+  }
+}
+
+void blink6OD() {
+  tBlink1.restartDelayed( 2 * TASK_SECOND );
+  LEDOff();
+}

+ 11 - 0
examples/Scheduler_example29_NonArduino/test/README

@@ -0,0 +1,11 @@
+
+This directory is intended for PlatformIO Unit Testing and project tests.
+
+Unit Testing is a software testing method by which individual units of
+source code, sets of one or more MCU program modules together with associated
+control data, usage procedures, and operating procedures, are tested to
+determine whether they are fit for use. Unit testing finds problems early
+in the development cycle.
+
+More information about PlatformIO Unit Testing:
+- https://docs.platformio.org/page/plus/unit-testing.html

+ 7 - 3
examples/Scheduler_template/Scheduler_template.ino

@@ -41,6 +41,7 @@
 // The following "defines" control library functionality at compile time,
 // and should be used in the main sketch depending on the functionality required
 // Should be defined BEFORE #include <TaskScheduler.h>  !!!
+// In PlatformIO - better define those as build_flags in platformio.ini file
 //
 // #define _TASK_TIMECRITICAL       // Enable monitoring scheduling overruns
 // #define _TASK_SLEEP_ON_IDLE_RUN  // Enable 1 ms SLEEP_IDLE powerdowns between runs if no callback methods were invoked during the pass
@@ -56,10 +57,13 @@
 // #define _TASK_OO_CALLBACKS       // Support for callbacks via inheritance
 // #define _TASK_EXPOSE_CHAIN       // Methods to access tasks in the task chain
 // #define _TASK_SCHEDULING_OPTIONS // Support for multiple scheduling options
-// #define _TASK_DEFINE_MILLIS      // Force forward declaration of millis() and micros() "C" style
-// #define _TASK_EXTERNAL_TIME      // Custom millis() and micros() methods
-// #define _TASK_THREAD_SAFE        // Enable additional checking for thread safety
 // #define _TASK_SELF_DESTRUCT      // Enable tasks to "self-destruct" after disable
+// #define _TASK_TICKLESS           // Enable support for tickless sleep on FreeRTOS
+// #define _TASK_DO_NOT_YIELD       // Disable yield() method in execute() for ESP chips
+// #define _TASK_ISR_SUPPORT        // for esp chips - place control methods in IRAM
+// #define _TASK_NON_ARDUINO        // for non-arduino use
+// #define _TASK_HEADER_AND_CPP     // compile CPP file (non-Arduino IDE platforms)
+// #define _TASK_THREAD_SAFE        // Enable additional checking for thread safety
 
 #include <TaskScheduler.h>
 

+ 2 - 4
keywords.txt

@@ -78,6 +78,7 @@ OnDisable	KEYWORD2
 OnEnable	KEYWORD2
 pause	KEYWORD2
 pending	KEYWORD2
+requestAction	KEYWORD2
 resetTimeout	KEYWORD2
 restart	KEYWORD2
 restartDelayed	KEYWORD2
@@ -124,13 +125,9 @@ _TASK_TIMECRITICAL	LITERAL1
 _TASK_TIMEOUT	LITERAL1
 _TASK_WDT_IDS	LITERAL1
 _TASK_EXPOSE_CHAIN	LITERAL1
-_TASK_DEFINE_MILLIS	LITERAL1
 _TASK_SCHEDULING_OPTIONS	LITERAL1
-_TASK_DEFINE_MILLIS	LITERAL1
-_TASK_EXTERNAL_TIME	LITERAL1
 _TASK_THREAD_SAFE	LITERAL1
 _TASK_SELF_DESTRUCT	LITERAL1
-SleepCallback	LITERAL1
 TASK_FOREVER	LITERAL1
 TASK_HOUR	LITERAL1
 TASK_IMMEDIATE	LITERAL1
@@ -139,6 +136,7 @@ TASK_MINUTE	LITERAL1
 TASK_NOTIMEOUT	LITERAL1
 TASK_ONCE	LITERAL1
 TASK_SECOND	LITERAL1
+SleepCallback	LITERAL1
 TaskCallback	LITERAL1
 TaskOnDisable	LITERAL1
 TaskOnEnable	LITERAL1

+ 1 - 1
library.json

@@ -16,7 +16,7 @@
       "maintainer": true
     }
   ],
-  "version": "3.9.0",
+  "version": "4.0.0",
   "frameworks": "arduino",
   "platforms": "*"
 }

+ 1 - 1
library.properties

@@ -1,5 +1,5 @@
 name=TaskScheduler
-version=3.9.0
+version=4.0.0
 author=Anatoli Arkhipenko <arkhipenko@hotmail.com>
 maintainer=Anatoli Arkhipenko <arkhipenko@hotmail.com>
 sentence=Cooperative multitasking for Arduino, ESPx, STM32 and other microcontrollers.

+ 3 - 0
src/TaskScheduler.cpp

@@ -0,0 +1,3 @@
+#ifdef _TASK_HEADER_AND_CPP
+#include <TaskScheduler.h>
+#endif

+ 350 - 211
src/TaskScheduler.h

@@ -258,19 +258,33 @@ v3.9.0:
                     Task::delay
                     Task::forceNextIteration
 
+v4.0.0:
+    2024-10-26 - MAJOR UPDATE for use in pre-emptive environments (FreeRTOS or Zephyr)
+        - list of IRAM-enabled methods extended:
+            Task::disable
+            Task::abort
+            Task::cancel
+        - _TASK_DEFINE_MILLIS - deprecated
+        - _TASK_EXTERNAL_TIME - deprecated
+        - New compile options:
+            _TASK_NON_ARDUINO - does not include "Arduino.h"
+                                target platform has to implement:
+                                    unsigned long _task_millis();
+                                    unsigned long _task_micros(); 
+                                    void _task_yield();
+            _TASK_HEADER_AND_CPP - enables compilation of TaskScheduler.cpp (non Arduino IDE use)
+
+        - Major rework of the _TASK_THREAD_SAFE approach. Developers should only be calling Scheduler and Task methods
+          directly from the thread where TaskScheduler execute() method runs. 
+          Calls from the other threads should be done via Scheduler::requestAction(...) methods. 
+          Target platform should implement a action queue and two methods:
+             bool _task_enqueue_request(_task_request_t* req);  // puts _task_request_t object on the action queue
+             bool _task_dequeue_request(_task_request_t* req);  // retrieves _task_request_t object from the action queue
+             Please see examples folder for implementation folder.
 */
 
-
 #include "TaskSchedulerDeclarations.h"
 
-#ifdef _TASK_DEFINE_MILLIS
-extern "C" {
-    unsigned long micros(void);
-    unsigned long millis(void);
-}
-#endif
-
-
 #ifndef _TASKSCHEDULER_H_
 #define _TASKSCHEDULER_H_
 
@@ -292,13 +306,13 @@ extern "C" {
 // #define _TASK_OO_CALLBACKS       // Support for callbacks via inheritance
 // #define _TASK_EXPOSE_CHAIN       // Methods to access tasks in the task chain
 // #define _TASK_SCHEDULING_OPTIONS // Support for multiple scheduling options
-// #define _TASK_DEFINE_MILLIS      // Force forward declaration of millis() and micros() "C" style
-// #define _TASK_EXTERNAL_TIME      // Custom millis() and micros() methods
-// #define _TASK_THREAD_SAFE        // Enable additional checking for thread safety
 // #define _TASK_SELF_DESTRUCT      // Enable tasks to "self-destruct" after disable
-// #define _TASK_TICKLESS           // Enable support for tickless sleep under FreeRTOS
-// #define _TASK_DO_NOT_YIELD       // Disable yield() method in execute()
-
+// #define _TASK_TICKLESS           // Enable support for tickless sleep on FreeRTOS
+// #define _TASK_DO_NOT_YIELD       // Disable yield() method in execute() for ESP chips
+// #define _TASK_ISR_SUPPORT        // for esp chips - place control methods in IRAM
+// #define _TASK_NON_ARDUINO        // for non-arduino use
+// #define _TASK_HEADER_AND_CPP     // PlatformIO style: separate Header and CPP file
+// #define _TASK_THREAD_SAFE        // Enable additional checking for thread safety
 
  #ifdef _TASK_MICRO_RES
 
@@ -328,7 +342,7 @@ extern "C" {
 #endif // ARDUINO_ARCH_ESP8266
 
 #ifdef _TASK_WDT_IDS
-    static unsigned int __task_id_counter = 0; // global task ID counter for assiging task IDs automatically.
+    static unsigned int  _task_id_counter = 0; // global task ID counter for assiging task IDs automatically.
 #endif  // _TASK_WDT_IDS
 
 #ifdef _TASK_PRIORITY
@@ -336,14 +350,44 @@ extern "C" {
 #endif // _TASK_PRIORITY
 
 
+#ifdef _TASK_THREAD_SAFE
+__attribute__((weak)) bool _task_enqueue_request(_task_request_t* req) {return false; }; 
+__attribute__((weak)) bool _task_dequeue_request(_task_request_t* req) {return false; }; 
+#endif
+
+
 // ------------------ TaskScheduler implementation --------------------
 
-#ifndef _TASK_EXTERNAL_TIME
-static uint32_t _task_millis() {return millis();}
+#ifndef _TASK_NON_ARDUINO
+
+#ifdef _TASK_MICRO_RES
+static unsigned long _task_micros() {return micros();}
+#else
+static unsigned long _task_millis() {return millis();}
+#if defined(_TASK_SLEEP_ON_IDLE_RUN) || defined(_TASK_TIMECRITICAL)
+static unsigned int _task_micros() {return micros();}
+#endif  // _TASK_SLEEP_ON_IDLE_RUN
+#endif  //  _TASK_MICRO_RES
+
+#if !defined(_TASK_DO_NOT_YIELD)
+static void _task_yield() { yield(); };
+#endif
+
+#else
+
 #ifdef _TASK_MICRO_RES
-static uint32_t _task_micros() {return micros();}
+static unsigned long _task_micros();
+#else
+static unsigned long _task_millis();
+#if defined(_TASK_SLEEP_ON_IDLE_RUN) || defined(_TASK_TIMECRITICAL)
+static unsigned long _task_micros();
+#endif  // _TASK_SLEEP_ON_IDLE_RUN
 #endif  //  _TASK_MICRO_RES
-#endif  //  _TASK_EXTERNAL_TIME
+
+#if !defined(_TASK_DO_NOT_YIELD)
+static void _task_yield();
+#endif
+#endif  // #ifndef _TASK_NON_ARDUINO
 
 /** Constructor, uses default values for the parameters
  * so could be called with no parameters.
@@ -375,7 +419,7 @@ Task::Task( unsigned long aInterval, long aIterations, TaskCallback aCallback, S
     if (aScheduler) aScheduler->addTask(*this);
 
 #ifdef _TASK_WDT_IDS
-    iTaskID = ++__task_id_counter;
+    iTaskID = ++ _task_id_counter;
 #endif  // _TASK_WDT_IDS
 
     if (aEnable) enable();
@@ -411,7 +455,7 @@ Task::Task( TaskCallback aCallback, Scheduler* aScheduler, TaskOnEnable aOnEnabl
     if (aScheduler) aScheduler->addTask(*this);
 
 #ifdef _TASK_WDT_IDS
-    iTaskID = ++__task_id_counter;
+    iTaskID = ++ _task_id_counter;
 #endif  // _TASK_WDT_IDS
 }
 
@@ -442,7 +486,7 @@ StatusRequest* Task::getInternalStatusRequest() { return &iMyStatusRequest; }
  *  Negative status will complete Status Request fully (since an error occured).
  *  @return: true, if StatusRequest is complete, false otherwise (still waiting for other events)
  */
-bool __TASK_IRAM StatusRequest::signal(int aStatus) {
+bool _TASK_IRAM StatusRequest::signal(int aStatus) {
     if ( iCount) {  // do not update the status request if it was already completed
         if (iCount > 0)  --iCount;
         if ( (iStatus = aStatus) < 0 ) iCount = 0;   // if an error is reported, the status is requested to be completed immediately
@@ -450,7 +494,7 @@ bool __TASK_IRAM StatusRequest::signal(int aStatus) {
     return (iCount == 0);
 }
 
-void __TASK_IRAM StatusRequest::signalComplete(int aStatus) {
+void _TASK_IRAM StatusRequest::signalComplete(int aStatus) {
     if (iCount) { // do not update the status request if it was already completed
         iCount = 0;
         iStatus = aStatus;
@@ -462,48 +506,24 @@ void __TASK_IRAM StatusRequest::signalComplete(int aStatus) {
  *  If aStatusRequest is NULL, request for waiting is ignored, and the waiting task is not enabled.
  */
 bool Task::waitFor(StatusRequest* aStatusRequest, unsigned long aInterval, long aIterations) {
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex + 1;
-#endif  // _TASK_THREAD_SAFE
-
     iStatusRequest = aStatusRequest;
     if ( iStatusRequest != NULL ) { // assign internal StatusRequest var and check if it is not NULL
         setIterations(aIterations);
         setInterval(aInterval);
         iStatus.waiting = _TASK_SR_NODELAY;  // no delay
-        
-#ifdef _TASK_THREAD_SAFE
-        iMutex = iMutex - 1;
-#endif  // _TASK_THREAD_SAFE
-        
         return enable();
     }
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex - 1;
-#endif  // _TASK_THREAD_SAFE
-
     return false;
 }
 
 bool Task::waitForDelayed(StatusRequest* aStatusRequest, unsigned long aInterval, long aIterations) {
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex + 1;
-#endif  // _TASK_THREAD_SAFE
-
     iStatusRequest = aStatusRequest;
     if ( iStatusRequest != NULL ) { // assign internal StatusRequest var and check if it is not NULL
         setIterations(aIterations);
-        if ( aInterval ) setInterval(aInterval);  // For the dealyed version only set the interval if it was not a zero
+        if ( aInterval ) setInterval(aInterval);  // For the delayed version only set the interval if it was not a zero
         iStatus.waiting = _TASK_SR_DELAY;  // with delay equal to the current interval
-#ifdef _TASK_THREAD_SAFE
-        iMutex = iMutex - 1;
-#endif  // _TASK_THREAD_SAFE
         return enable();
     }
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex - 1;
-#endif  // _TASK_THREAD_SAFE
-
     return false;
 }
 
@@ -550,10 +570,6 @@ void Task::setOnDisable(TaskOnDisable aCallback) { iOnDisable = aCallback; }
  */
 void Task::reset() {
   
-#ifdef _TASK_THREAD_SAFE
-    iMutex = 1;
-#endif  // _TASK_THREAD_SAFE
-
     iStatus.enabled = false;
     iStatus.inonenable = false;
     iStatus.canceled = false;
@@ -594,10 +610,6 @@ void Task::reset() {
     iStatus.timeout = false;
 #endif  // _TASK_TIMEOUT
 
-#ifdef _TASK_THREAD_SAFE
-    iMutex = 0;
-#endif  // _TASK_THREAD_SAFE
-
 #ifdef _TASK_SELF_DESTRUCT
     iStatus.sd_request = false;
 #endif  //  #ifdef _TASK_SELF_DESTRUCT
@@ -616,30 +628,12 @@ void Task::reset() {
 void Task::set(unsigned long aInterval, long aIterations) {
 #else
 void Task::set(unsigned long aInterval, long aIterations, TaskCallback aCallback, TaskOnEnable aOnEnable, TaskOnDisable aOnDisable) {
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex + 1;
-#endif  // _TASK_THREAD_SAFE
-
     iCallback = aCallback;
     iOnEnable = aOnEnable;
     iOnDisable = aOnDisable;
-
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex - 1;
-#endif  // _TASK_THREAD_SAFE
-
 #endif // _TASK_OO_CALLBACKS
-
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex + 1;
-#endif  // _TASK_THREAD_SAFE
-
     setInterval(aInterval);
     iSetIterations = iIterations = aIterations;
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex - 1;
-#endif  // _TASK_THREAD_SAFE
-
 }
 
 /** Sets number of iterations for the task
@@ -647,15 +641,7 @@ void Task::set(unsigned long aInterval, long aIterations, TaskCallback aCallback
  * @param aIterations - number of iterations, use -1 for no limit
  */
 void Task::setIterations(long aIterations) {
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex + 1;
-#endif  // _TASK_THREAD_SAFE
-
     iSetIterations = iIterations = aIterations;
-
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex - 1;
-#endif  // _TASK_THREAD_SAFE
 }
 
 #ifndef _TASK_OO_CALLBACKS
@@ -664,10 +650,6 @@ void Task::setIterations(long aIterations) {
  * @param aCallback - pointer to the callback method for the next step
  */
 void Task::yield (TaskCallback aCallback) {
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex + 1;
-#endif  // _TASK_THREAD_SAFE
-
     iCallback = aCallback;
     forceNextIteration();
 
@@ -676,26 +658,14 @@ void Task::yield (TaskCallback aCallback) {
     // a series of callback methods
     iRunCounter = iRunCounter - 1;
     if ( iIterations >= 0 ) iIterations = iIterations + 1;
-
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex - 1;
-#endif  // _TASK_THREAD_SAFE
 }
 
 /** Prepare task for next step iteration following yielding of control to the scheduler
  * @param aCallback - pointer to the callback method for the next step
  */
 void Task::yieldOnce (TaskCallback aCallback) {
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex + 1;
-#endif  // _TASK_THREAD_SAFE
-
     yield(aCallback);
     iIterations = 1;
-
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex - 1;
-#endif  // _TASK_THREAD_SAFE
 }
 #endif // _TASK_OO_CALLBACKS
 
@@ -704,13 +674,8 @@ void Task::yieldOnce (TaskCallback aCallback) {
  *  schedules it for execution as soon as possible,
  *  and resets the RunCounter back to zero
  */
-bool __TASK_IRAM Task::enable() {
+bool _TASK_IRAM Task::enable() {
     if (iScheduler) { // activation without active scheduler does not make sense
-
-#ifdef _TASK_THREAD_SAFE
-        iMutex = iMutex + 1;
-#endif  // _TASK_THREAD_SAFE
-
         iRunCounter = 0;
         iStatus.canceled = false;
         
@@ -753,11 +718,6 @@ bool __TASK_IRAM Task::enable() {
             iMyStatusRequest.signalComplete();
         }
 #endif // _TASK_STATUS_REQUEST
-
-#ifdef _TASK_THREAD_SAFE
-        iMutex = iMutex - 1;
-#endif  // _TASK_THREAD_SAFE
-
         return iStatus.enabled;
     }
     return false;
@@ -766,64 +726,30 @@ bool __TASK_IRAM Task::enable() {
 /** Enables the task only if it was not enabled already
  * Returns previous state (true if was already enabled, false if was not)
  */
-bool __TASK_IRAM Task::enableIfNot() {
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex + 1;
-#endif  // _TASK_THREAD_SAFE
-
+bool _TASK_IRAM Task::enableIfNot() {
     bool previousEnabled = iStatus.enabled;
     if ( !previousEnabled ) enable();
-
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex - 1;
-#endif  // _TASK_THREAD_SAFE
-
     return (previousEnabled);
 }
 
 /** Enables the task
  * and schedules it for execution after a delay = aInterval
  */
-bool __TASK_IRAM Task::enableDelayed(unsigned long aDelay) {
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex + 1;
-#endif  // _TASK_THREAD_SAFE
-
+bool _TASK_IRAM Task::enableDelayed(unsigned long aDelay) {
     enable();
     delay(aDelay);
-
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex - 1;
-#endif  // _TASK_THREAD_SAFE
-
     return iStatus.enabled;
 }
 
 #ifdef _TASK_TIMEOUT
 void Task::setTimeout(unsigned long aTimeout, bool aReset) {
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex + 1;
-#endif  // _TASK_THREAD_SAFE
-
     iTimeout = aTimeout;
     if (aReset) resetTimeout();
-
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex - 1;
-#endif  // _TASK_THREAD_SAFE
 }
 
 void Task::resetTimeout() {
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex + 1;
-#endif  // _TASK_THREAD_SAFE
-
     iStarttime = _TASK_TIME_FUNCTION();
     iStatus.timeout = false;
-
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex - 1;
-#endif  // _TASK_THREAD_SAFE
 }
 
 unsigned long Task::getTimeout() {
@@ -849,28 +775,15 @@ bool Task::timedOut() {
  * leaves task enabled or disabled
  * if aDelay is zero, delays for the original scheduling interval from now
  */
-void __TASK_IRAM Task::delay(unsigned long aDelay) {
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex + 1;
-#endif  // _TASK_THREAD_SAFE
-
+void _TASK_IRAM Task::delay(unsigned long aDelay) {
     iDelay = aDelay ? aDelay : iInterval;
     iPreviousMillis = _TASK_TIME_FUNCTION(); 
-
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex - 1;
-#endif  // _TASK_THREAD_SAFE
 }
 
 /** Adjusts Task execution with aInterval (if task is enabled).
  */
 void Task::adjust(long aInterval) {
     if ( aInterval == 0 ) return;  //  nothing to do for a zero
-
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex + 1;
-#endif  // _TASK_THREAD_SAFE
-
     if ( aInterval < 0 ) {
       iPreviousMillis = iPreviousMillis + aInterval;
     }
@@ -878,9 +791,6 @@ void Task::adjust(long aInterval) {
       iDelay = iDelay + aInterval;  //  we have to adjust delay because adjusting iPreviousMillis might push
                             //  it into the future beyond current millis() and cause premature trigger
     }
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex - 1;
-#endif  // _TASK_THREAD_SAFE
 }
 
 
@@ -888,17 +798,9 @@ void Task::adjust(long aInterval) {
  * leaves task enabled or disabled
  * Task's original schedule is shifted, and all subsequent iterations will continue from this point in time
  */
-void __TASK_IRAM Task::forceNextIteration() {
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex + 1;
-#endif  // _TASK_THREAD_SAFE
-
+void _TASK_IRAM Task::forceNextIteration() {
     iDelay = iInterval;
     iPreviousMillis = _TASK_TIME_FUNCTION() - iDelay;
-
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex - 1;
-#endif  // _TASK_THREAD_SAFE
 }
 
 /** Sets the execution interval.
@@ -907,16 +809,8 @@ void __TASK_IRAM Task::forceNextIteration() {
  * @param aInterval - new execution interval
  */
 void Task::setInterval (unsigned long aInterval) {
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex + 1;
-#endif  // _TASK_THREAD_SAFE
-
     iInterval = aInterval;
     delay(); // iDelay will be updated by the delay() function
-
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex - 1;
-#endif  // _TASK_THREAD_SAFE
 }
 
 /** Sets the execution interval without delaying the task
@@ -925,10 +819,6 @@ void Task::setInterval (unsigned long aInterval) {
  * @param aInterval - new execution interval
  */
 void Task::setIntervalNodelay (unsigned long aInterval, unsigned int aOption) {
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex + 1;
-#endif  // _TASK_THREAD_SAFE
-
 // #define TASK_INTERVAL_KEEP      0
 // #define TASK_INTERVAL_RECALC    1
 // #define TASK_INTERVAL_RESET     2
@@ -958,10 +848,6 @@ void Task::setIntervalNodelay (unsigned long aInterval, unsigned int aOption) {
           }
           break;
     }
-
-#ifdef _TASK_THREAD_SAFE
-    iMutex = iMutex - 1;
-#endif  // _TASK_THREAD_SAFE
 }
 
 /** Disables task
@@ -969,7 +855,7 @@ void Task::setIntervalNodelay (unsigned long aInterval, unsigned int aOption) {
  * Returns status of the task before disable was called (i.e., if the task was already disabled)
  */
 
-bool Task::disable() {
+bool _TASK_IRAM Task::disable() {
     bool previousEnabled = iStatus.enabled;
     iStatus.enabled = false;
     iStatus.inonenable = false;
@@ -1003,7 +889,7 @@ bool Task::disable() {
 /** Aborts task execution
  * Task will no longer be executed by the scheduler AND ondisable method will not be called
  */
-void Task::abort() {
+void _TASK_IRAM Task::abort() {
     iStatus.enabled = false;
     iStatus.inonenable = false;
     iStatus.canceled = true;
@@ -1020,7 +906,7 @@ void Task::abort() {
 /** Cancels task execution
  * Task will no longer be executed by the scheduler. Ondisable method will be called after 'canceled' flag is set
  */
-void Task::cancel() {
+void _TASK_IRAM Task::cancel() {
     iStatus.canceled = true;
 #ifdef _TASK_STATUS_REQUEST
     iMyStatusRequest.signalComplete(TASK_SR_ABORT);
@@ -1036,7 +922,7 @@ bool Task::canceled() {
  * Task will run number of iterations again
  */
 
-bool __TASK_IRAM Task::restart() {
+bool _TASK_IRAM Task::restart() {
     iIterations = iSetIterations;
     return enable();
 }
@@ -1044,7 +930,7 @@ bool __TASK_IRAM Task::restart() {
 /** Restarts task delayed
  * Task will run number of iterations again
  */
-bool __TASK_IRAM Task::restartDelayed(unsigned long aDelay) {
+bool _TASK_IRAM Task::restartDelayed(unsigned long aDelay) {
     iIterations = iSetIterations;
     return enableDelayed(aDelay);
 }
@@ -1331,14 +1217,14 @@ void* Scheduler::currentLts() { return iCurrent->iLTS; }
 bool Scheduler::isOverrun() { return (iCurrent->iOverrun < 0); }
 
 void Scheduler::cpuLoadReset() {
-    iCPUStart = micros();
+    iCPUStart = _task_micros();
     iCPUCycle = 0;
     iCPUIdle = 0;
 }
 
 
 unsigned long Scheduler::getCpuLoadTotal() {
-    return (micros() - iCPUStart);
+    return (_task_micros() - iCPUStart);
 }
 #endif  // _TASK_TIMECRITICAL
 
@@ -1352,6 +1238,252 @@ void  Scheduler::setSleepMethod( SleepCallback aCallback ) {
 }
 #endif  // _TASK_SLEEP_ON_IDLE_RUN
 
+#ifdef  _TASK_THREAD_SAFE
+void Scheduler::requestAction(_task_request_t* aRequest) {
+    if ( aRequest == NULL ) return; 
+    _task_enqueue_request(aRequest);
+}
+
+void Scheduler::requestAction(void* aObject, _task_request_type_t aType, unsigned long aParam1, unsigned long aParam2, unsigned long aParam3, unsigned long aParam4, unsigned long aParam5) {
+    if ( aObject == NULL ) return; 
+    _task_request_t r = {
+        .req_type = aType,
+        .object_ptr = aObject,
+        .param1 = aParam1,
+        .param2 = aParam2,
+        .param3 = aParam3,
+        .param4 = aParam4,
+        .param5 = aParam5
+    };
+    _task_enqueue_request(&r);
+}
+
+void Scheduler::processRequests() {
+    _task_request_t req;
+
+    while ( _task_dequeue_request(&req) ) {
+        switch (req.req_type ) {
+
+#ifdef _TASK_STATUS_REQUEST
+        case TASK_SR_REQUEST_SETWAITING_1: {
+            StatusRequest* t = (StatusRequest*) req.object_ptr;
+            t->setWaiting(req.param1);
+        }
+        break;
+
+        case TASK_SR_REQUEST_SIGNAL_1: {
+            StatusRequest* t = (StatusRequest*) req.object_ptr;
+            t->signal((int)req.param1);
+        }
+        break;
+
+        case TASK_SR_REQUEST_SIGNALCOMPLETE_1: {
+            StatusRequest* t = (StatusRequest*) req.object_ptr;
+            t->signalComplete((int) req.param1);
+        }
+        break;
+
+#ifdef _TASK_TIMEOUT
+        case TASK_SR_REQUEST_SETTIMEOUT_1: {
+            StatusRequest* t = (StatusRequest*) req.object_ptr;
+            t->setTimeout(req.param1);
+        }
+        break;
+
+        case TASK_SR_REQUEST_RESETTIMEOUT_0: {
+            StatusRequest* t = (StatusRequest*) req.object_ptr;
+            t->resetTimeout();
+        }
+        break;
+#endif  // _TASK_TIMEOUT
+
+#endif
+
+#ifdef _TASK_LTS_POINTER
+        case TASK_REQUEST_SETLTSPOINTER_1: {
+            Task* t = (Task*) req.object_ptr;
+            t->setLtsPointer((void*) req.param1);
+        }
+        break;
+#endif
+
+#ifdef _TASK_SELF_DESTRUCT
+        case TASK_REQUEST_SETSELFDESTRUCT_1: {
+            Task* t = (Task*) req.object_ptr;
+            t->setSchedulingOption((bool)req.param1);
+        }
+        break;
+#endif
+
+#ifdef _TASK_SCHEDULING_OPTIONS
+        case TASK_REQUEST_SETSCHEDULINGOPTION_1: {
+            Task* t = (Task*) req.object_ptr;
+            t->setSchedulingOption((unsigned int)req.param1);
+        }
+        break;
+#endif
+
+#ifdef _TASK_TIMEOUT
+        case TASK_REQUEST_SETTIMEOUT_2: {
+            Task* t = (Task*) req.object_ptr;
+            t->setTimeout(req.param1, (bool)req.param2);
+        }
+        break;
+
+        case TASK_REQUEST_RESETTIMEOUT_0: {
+            Task* t = (Task*) req.object_ptr;
+            t->resetTimeout();
+        }
+        break;
+#endif
+
+#ifdef _TASK_STATUS_REQUEST
+        case TASK_REQUEST_WAITFOR: {
+            Task* t = (Task*) req.object_ptr;
+            t->waitFor((StatusRequest*)req.param1, req.param2, req.param3);
+        }
+        break;
+
+        case TASK_REQUEST_WAITFORDELAYED_3: {
+            Task* t = (Task*) req.object_ptr;
+            t->waitForDelayed((StatusRequest*)req.param1, req.param2, (long) req.param3);
+        }
+        break;
+#endif
+
+#ifdef _TASK_WDT_IDS
+        case TASK_REQUEST_SETID_1: {
+            Task* t = (Task*) req.object_ptr;
+            t->setId((unsigned int)req.param1);
+        }
+        break; 
+
+        case TASK_REQUEST_SETCONTROLPOINT_1: {
+            Task* t = (Task*) req.object_ptr;
+            t->setControlPoint((unsigned int)req.param1);
+        }
+        break;
+#endif
+
+        case TASK_REQUEST_ENABLE_0: {
+            Task* t = (Task*) req.object_ptr;
+            t->enable();
+        }
+        break;
+
+        case TASK_REQUEST_ENABLEIFNOT_0: {
+            Task* t = (Task*) req.object_ptr;
+            t->enableIfNot();
+        }
+        break;
+        
+        case TASK_REQUEST_ENABLEDELAYED_1: {
+            Task* t = (Task*) req.object_ptr;
+            t->enableDelayed(req.param1);
+        }
+        break;
+        
+        case TASK_REQUEST_RESTART_0: {
+            Task* t = (Task*) req.object_ptr;
+            t->restart();
+        }
+        break;
+        
+        case TASK_REQUEST_RESTARTDELAYED_1: {
+            Task* t = (Task*) req.object_ptr;
+            t->restartDelayed(req.param1);
+        }
+        break;
+        
+        case TASK_REQUEST_DELAY_1: {
+            Task* t = (Task*) req.object_ptr;
+            t->delay(req.param1);
+        }
+        break;
+        
+        case TASK_REQUEST_ADJUST_1: {
+            Task* t = (Task*) req.object_ptr;
+            t->adjust(req.param1);
+        }
+        break;
+        
+        case TASK_REQUEST_FORCENEXTITERATION_0: {
+            Task* t = (Task*) req.object_ptr;
+            t->forceNextIteration();
+        }
+        break;
+
+        case TASK_REQUEST_DISABLE_0: {
+            Task* t = (Task*) req.object_ptr;
+            t->disable();
+        }
+        break;
+
+        case TASK_REQUEST_ABORT_0: {
+            Task* t = (Task*) req.object_ptr;
+            t->abort();
+        }
+        break;
+
+        case TASK_REQUEST_CANCEL_0: {
+            Task* t = (Task*) req.object_ptr;
+            t->cancel();
+        }
+        break;
+
+        case TASK_REQUEST_SET_5:{
+            Task* t = (Task*) req.object_ptr;
+#ifdef _TASK_OO_CALLBACKS
+            t->set(req.param1, (long)req.param2);
+#else
+            t->set(req.param1, (long)req.param2, (TaskCallback) req.param3, (TaskOnEnable) req.param4, (TaskOnDisable) req.param5);
+#endif
+        }
+        break;
+
+        case TASK_REQUEST_SETINTERVAL_1: {
+            Task* t = (Task*) req.object_ptr;
+            t->setInterval(req.param1);
+        }
+        break;
+
+        case TASK_REQUEST_SETINTERVALNODELAY_2: {
+            Task* t = (Task*) req.object_ptr;
+            t->setIntervalNodelay(req.param1, (unsigned int)req.param2);
+        }
+        break;
+
+        case TASK_REQUEST_SETITERATIONS_1: {
+            Task* t = (Task*) req.object_ptr;
+            t->setIterations((long)req.param1);
+        }
+        break;
+
+        case TASK_REQUEST_SETCALLBACK_1: {
+            Task* t = (Task*) req.object_ptr;
+            t->setCallback((TaskCallback)req.param1);
+        }
+        break;
+
+        case TASK_REQUEST_SETONENABLE_1: {
+            Task* t = (Task*) req.object_ptr;
+            t->setOnEnable((TaskOnEnable)req.param1);
+        }
+        break;
+
+        case TASK_REQUEST_SETONDISABLE_1: {
+            Task* t = (Task*) req.object_ptr;
+            t->setOnDisable((TaskOnDisable)req.param1);
+        }
+        break;
+
+        default:
+        break;
+        }
+    }
+}
+#endif  // _TASK_THREAD_SAFE
+
 
 /** Makes one pass through the execution chain.
  * Tasks are executed in the order they were added to the chain
@@ -1395,9 +1527,15 @@ bool Scheduler::execute() {
     //  after the higher priority scheduler has been invoked.
     if ( !iEnabled ) return true; //  consider this to be an idle run
 
+#ifdef _TASK_THREAD_SAFE
+    // Process external requests for task updates 
+    // The requests are processed in bulk, in the order they were received
+    processRequests();
+#endif
+
 #ifdef _TASK_SLEEP_ON_IDLE_RUN
     // scheduling pass starts
-    tStart = micros();
+    tStart = _task_micros();
 #endif
 
 #ifdef _TASK_TICKLESS
@@ -1408,10 +1546,17 @@ bool Scheduler::execute() {
 
     while (!iPaused && iCurrent) {
 
+#ifdef _TASK_THREAD_SAFE
+    // Process external requests for task on every pass
+    // The requests are processed in bulk, in the order they were received
+    // This should emulate a one-thread behaviour
+        processRequests();
+#endif
+
         iTotalTasks++;
 
 #if defined(_TASK_TIMECRITICAL)
-        tPassStart = micros();
+        tPassStart = _task_micros();
         tTaskStart = tTaskFinish = 0; 
 #endif  // _TASK_TIMECRITICAL
 
@@ -1425,12 +1570,6 @@ bool Scheduler::execute() {
             if ( iCurrent->iStatus.enabled ) {
                 iActiveTasks++;
 
-#ifdef _TASK_THREAD_SAFE
-            //  this task is in the scheduling state and should not be invoked
-            //  as there could be incosistent settings until scheduling is done
-            if ( iCurrent->iMutex ) break;
-#endif  // _TASK_THREAD_SAFE
-
 #ifdef _TASK_WDT_IDS
     // For each task the control points are initialized to avoid confusion because of carry-over:
                 iCurrent->iControlPoint = 0;
@@ -1554,7 +1693,7 @@ bool Scheduler::execute() {
                 iCurrent->iDelay = i;
 
 #if defined(_TASK_TIMECRITICAL)
-                tTaskStart = micros();
+                tTaskStart = _task_micros();
 #endif  // _TASK_TIMECRITICAL
 
 #ifdef _TASK_OO_CALLBACKS
@@ -1568,7 +1707,7 @@ bool Scheduler::execute() {
 #endif // _TASK_OO_CALLBACKS
 
 #if defined(_TASK_TIMECRITICAL)
-                tTaskFinish = micros();
+                tTaskFinish = _task_micros();
 #endif  // _TASK_TIMECRITICAL
 
             }
@@ -1581,18 +1720,18 @@ bool Scheduler::execute() {
         
         
 #ifdef _TASK_TIMECRITICAL
-        iCPUCycle += ( (micros() - tPassStart) - (tTaskFinish - tTaskStart) );
+        iCPUCycle += ( (_task_micros() - tPassStart) - (tTaskFinish - tTaskStart) );
 #endif  // _TASK_TIMECRITICAL
         
 #if defined (ARDUINO_ARCH_ESP8266) || defined (ARDUINO_ARCH_ESP32) 
 #if !defined(_TASK_DO_NOT_YIELD)
-        yield();
+        _task_yield();
 #endif  //  _TASK_DO_NOT_YIELD
 #endif  //  ARDUINO_ARCH_ESPxx
     }
 
 #ifdef _TASK_SLEEP_ON_IDLE_RUN
-    tFinish = micros(); // Scheduling pass end time in microseconds.
+    tFinish = _task_micros(); // Scheduling pass end time in microseconds.
 #endif
 
 #ifdef _TASK_TICKLESS
@@ -1602,7 +1741,7 @@ bool Scheduler::execute() {
         if ( !idleRun ) break;
         if ( (nrd & _TASK_NEXTRUN_IMMEDIATE) ) break;
         if ( nrd == _TASK_NEXTRUN_UNDEFINED ) break;
-        m = millis();
+        m = _TASK_TIME_FUNCTION();
         if ( nr <= m) break;
         iNextRun = ( nr - m );
     } while (0);
@@ -1615,13 +1754,13 @@ bool Scheduler::execute() {
             if ( iSleepMethod != NULL ) {
                 
 #ifdef _TASK_TIMECRITICAL
-                tIdleStart = micros();
+                tIdleStart = _task_micros();
 #endif  // _TASK_TIMECRITICAL
 
                 (*iSleepMethod)( tFinish-tStart );
                 
 #ifdef _TASK_TIMECRITICAL
-                iCPUIdle += (micros() - tIdleStart);
+                iCPUIdle += (_task_micros() - tIdleStart);
 #endif  // _TASK_TIMECRITICAL
             }
         }

+ 321 - 141
src/TaskSchedulerDeclarations.h

@@ -1,10 +1,6 @@
 // Cooperative multitasking library for Arduino
 // Copyright (c) 2015-2023 Anatoli Arkhipenko
 
-#include <stddef.h>
-#include <stdint.h>
-#include <Arduino.h>
-
 #ifndef _TASKSCHEDULERDECLARATIONS_H_
 #define _TASKSCHEDULERDECLARATIONS_H_
 
@@ -26,13 +22,26 @@
 // #define _TASK_OO_CALLBACKS       // Support for callbacks via inheritance
 // #define _TASK_EXPOSE_CHAIN       // Methods to access tasks in the task chain
 // #define _TASK_SCHEDULING_OPTIONS // Support for multiple scheduling options
-// #define _TASK_DEFINE_MILLIS      // Force forward declaration of millis() and micros() "C" style
-// #define _TASK_EXTERNAL_TIME      // Custom millis() and micros() methods
-// #define _TASK_THREAD_SAFE        // Enable additional checking for thread safety
 // #define _TASK_SELF_DESTRUCT      // Enable tasks to "self-destruct" after disable
 // #define _TASK_TICKLESS           // Enable support for tickless sleep on FreeRTOS
-// #define _TASK_DO_NOT_YIELD       // Disable yield() method in execute()
+// #define _TASK_DO_NOT_YIELD       // Disable yield() method in execute() for ESP chips
 // #define _TASK_ISR_SUPPORT        // for esp chips - place control methods in IRAM
+// #define _TASK_NON_ARDUINO        // for non-arduino use
+// #define _TASK_HEADER_AND_CPP     // compile CPP file (non-Arduino IDE platforms)
+// #define _TASK_THREAD_SAFE        // Enable additional checking for thread safety
+
+#ifdef _TASK_NON_ARDUINO
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#else 
+#include <Arduino.h>
+#endif
 
 class Scheduler;
 
@@ -65,33 +74,23 @@ class Scheduler;
 #endif // _TASK_PRIORITY
 
 #ifdef _TASK_INLINE
-#define __INLINE  inline
+#define _TASK_INLINE  inline
 #else
-#define __INLINE
+#define _TASK_INLINE
 #endif
 
 #ifdef _TASK_ISR_SUPPORT
 #if defined (ARDUINO_ARCH_ESP8266)
-#define __TASK_IRAM ICACHE_RAM_ATTR
+#define _TASK_IRAM ICACHE_RAM_ATTR
 #endif 
 #if  defined (ARDUINO_ARCH_ESP32)
-#define __TASK_IRAM IRAM_ATTR
+#define _TASK_IRAM IRAM_ATTR
 #endif
 #endif
-#ifndef __TASK_IRAM
-#define __TASK_IRAM
+#ifndef _TASK_IRAM
+#define _TASK_IRAM
 #endif
 
-
-#ifdef _TASK_EXTERNAL_TIME
-uint32_t external_millis();
-#define _task_millis()  external_millis()
-#ifdef _TASK_MICRO_RES
-uint32_t external_micros();
-#define _task_micros()  external_micros()
-#endif  //  _TASK_MICRO_RES
-#endif  //  _TASK_EXTERNAL_TIME
-
 #ifndef _TASK_MICRO_RES
 
 #define TASK_MILLISECOND       1UL
@@ -114,6 +113,186 @@ uint32_t external_micros();
 #define _TASK_NEXTRUN_TIMED     0x10
 #endif  //  _TASK_TICKLESS
 
+#ifdef _TASK_THREAD_SAFE
+
+typedef enum {
+#ifdef _TASK_STATUS_REQUEST
+    TASK_SR_REQUEST_SETWAITING,
+    TASK_SR_REQUEST_SIGNAL,
+    TASK_SR_REQUEST_SIGNALCOMPLETE,
+
+#ifdef _TASK_TIMEOUT
+    TASK_SR_REQUEST_SETTIMEOUT,
+    TASK_SR_REQUEST_RESETTIMEOUT,
+#endif  // _TASK_TIMEOUT
+
+#endif  // _TASK_STATUS_REQUEST
+
+#ifdef _TASK_LTS_POINTER
+    TASK_REQUEST_SETLTSPOINTER,
+#endif
+
+#ifdef _TASK_SELF_DESTRUCT
+    TASK_REQUEST_SETSELFDESTRUCT,
+#endif  // _TASK_SELF_DESTRUCT
+
+#ifdef _TASK_SCHEDULING_OPTIONS
+    TASK_REQUEST_SETSCHEDULINGOPTION,
+#endif  // _TASK_SCHEDULING_OPTIONS
+
+#ifdef _TASK_TIMEOUT
+    TASK_REQUEST_SETTIMEOUT,
+    TASK_REQUEST_RESETTIMEOUT,
+#endif  // _TASK_TIMEOUT
+
+#ifdef _TASK_STATUS_REQUEST
+    TASK_REQUEST_WAITFOR,
+    TASK_REQUEST_WAITFORDELAYED,
+#endif  // _TASK_STATUS_REQUEST
+
+#ifdef _TASK_WDT_IDS
+    TASK_REQUEST_SETID,
+    TASK_REQUEST_SETCONTROLPOINT,
+#endif  // _TASK_WDT_IDS
+
+    TASK_REQUEST_ENABLE,
+    TASK_REQUEST_ENABLEIFNOT,
+    TASK_REQUEST_ENABLEDELAYED,
+    TASK_REQUEST_RESTART,
+    TASK_REQUEST_RESTARTDELAYED,
+    TASK_REQUEST_DELAY,
+    TASK_REQUEST_ADJUST,
+    TASK_REQUEST_FORCENEXTITERATION,
+    TASK_REQUEST_DISABLE,
+    TASK_REQUEST_ABORT,
+    TASK_REQUEST_CANCEL,
+    TASK_REQUEST_SET,
+    TASK_REQUEST_SETINTERVAL,
+    TASK_REQUEST_SETINTERVALNODELAY,
+    TASK_REQUEST_SETITERATIONS,
+    TASK_REQUEST_SETCALLBACK,
+    TASK_REQUEST_SETONENABLE,
+    TASK_REQUEST_SETONDISABLE
+} _task_request_type_t;
+
+#ifdef _TASK_STATUS_REQUEST
+// void setWaiting(unsigned int aCount = 1);
+#define TASK_SR_REQUEST_SETWAITING_1        TASK_SR_REQUEST_SETWAITING
+#define TASK_SR_REQUEST_SETWAITING_1_DEFAULT   1
+// _TASK_IRAM signal(int aStatus = 0);
+#define TASK_SR_REQUEST_SIGNAL_1            TASK_SR_REQUEST_SIGNAL
+#define TASK_SR_REQUEST_SIGNAL_1_DEFAULT    0
+// _TASK_IRAM signalComplete(int aStatus = 0);
+#define TASK_SR_REQUEST_SIGNALCOMPLETE_1    TASK_SR_REQUEST_SIGNALCOMPLETE
+#define TASK_SR_REQUEST_SIGNALCOMPLETE_1_DEFAULT    0
+
+#ifdef _TASK_TIMEOUT
+// void setTimeout(unsigned long aTimeout)
+#define TASK_SR_REQUEST_SETTIMEOUT_1        TASK_SR_REQUEST_SETTIMEOUT
+// void resetTimeout();
+#define TASK_SR_REQUEST_RESETTIMEOUT_0      TASK_SR_REQUEST_RESETTIMEOUT
+#endif  // _TASK_TIMEOUT
+
+#endif
+
+#ifdef _TASK_LTS_POINTER
+// void  setLtsPointer(void *aPtr);
+#define TASK_REQUEST_SETLTSPOINTER_1        TASK_REQUEST_SETLTSPOINTER
+#endif
+
+#ifdef _TASK_SELF_DESTRUCT
+// void setSelfDestruct(bool aSelfDestruct=true) 
+#define TASK_REQUEST_SETSELFDESTRUCT_1      TASK_REQUEST_SETSELFDESTRUCT
+#define TASK_REQUEST_SETSELFDESTRUCT_1_DEFAULT  true
+#endif
+
+#ifdef _TASK_SCHEDULING_OPTIONS
+// void setSchedulingOption(unsigned int aOption)
+#define TASK_REQUEST_SETSCHEDULINGOPTION_1  TASK_REQUEST_SETSCHEDULINGOPTION
+#endif
+
+#ifdef _TASK_TIMEOUT
+// void setTimeout(unsigned long aTimeout, bool aReset=false);
+#define TASK_REQUEST_SETTIMEOUT_2           TASK_REQUEST_SETTIMEOUT
+#define TASK_REQUEST_SETTIMEOUT_2_DEFAULT   false
+// void resetTimeout();
+#define TASK_REQUEST_RESETTIMEOUT_0         TASK_REQUEST_RESETTIMEOUT
+#endif  // _TASK_TIMEOUT
+
+#ifdef _TASK_STATUS_REQUEST
+// bool waitFor(StatusRequest* aStatusRequest, unsigned long aInterval = 0, long aIterations = 1)
+#define TASK_REQUEST_WAITFOR_3              TASK_REQUEST_WAITFOR
+#define TASK_REQUEST_WAITFOR_2_DEFAULT  0
+#define TASK_REQUEST_WAITFOR_3_DEFAULT  1
+// bool waitForDelayed(StatusRequest* aStatusRequest, unsigned long aInterval = 0, long aIterations = 1)
+#define TASK_REQUEST_WAITFORDELAYED_3       TASK_REQUEST_WAITFORDELAYED
+#define TASK_REQUEST_WAITFORDELAYED_2_DEFAULT   0
+#define TASK_REQUEST_WAITFORDELAYED_3_DEFAULT   1
+#endif  // _TASK_STATUS_REQUEST
+
+#ifdef _TASK_WDT_IDS
+// _TASK_INLINE void setId(unsigned int aID) ;
+#define TASK_REQUEST_SETID_1                TASK_REQUEST_SETID
+// _TASK_INLINE void setControlPoint(unsigned int aPoint) ;
+#define TASK_REQUEST_SETCONTROLPOINT_1      TASK_REQUEST_SETCONTROLPOINT
+#endif  // _TASK_WDT_IDS
+
+// bool _TASK_IRAM enable();
+#define TASK_REQUEST_ENABLE_0               TASK_REQUEST_ENABLE
+// bool _TASK_IRAM enableIfNot();
+#define TASK_REQUEST_ENABLEIFNOT_0          TASK_REQUEST_ENABLEIFNOT
+// bool _TASK_IRAM enableDelayed(unsigned long aDelay=0);
+#define TASK_REQUEST_ENABLEDELAYED_1        TASK_REQUEST_ENABLEDELAYED
+#define TASK_REQUEST_ENABLEDELAYED_1_DEFAULT    0
+// bool _TASK_IRAM restart();
+#define TASK_REQUEST_RESTART_0              TASK_REQUEST_RESTART
+// bool _TASK_IRAM restartDelayed(unsigned long aDelay=0);
+#define TASK_REQUEST_RESTARTDELAYED_1       TASK_REQUEST_RESTARTDELAYED
+#define TASK_REQUEST_RESTARTDELAYED_1_DEFAULT   0
+// void _TASK_IRAM delay(unsigned long aDelay=0);
+#define TASK_REQUEST_DELAY_1                TASK_REQUEST_DELAY
+#define TASK_REQUEST_DELAY_1_DEFAULT    0
+// void adjust(long aInterval);
+#define TASK_REQUEST_ADJUST_1               TASK_REQUEST_ADJUST
+// void _TASK_IRAM forceNextIteration();
+#define TASK_REQUEST_FORCENEXTITERATION_0   TASK_REQUEST_FORCENEXTITERATION
+// bool disable();
+#define TASK_REQUEST_DISABLE_0              TASK_REQUEST_DISABLE
+// void abort();
+#define TASK_REQUEST_ABORT_0                TASK_REQUEST_ABORT
+// void cancel();
+#define TASK_REQUEST_CANCEL_0               TASK_REQUEST_CANCEL
+// void set(unsigned long aInterval, long aIterations, TaskCallback aCallback,TaskOnEnable aOnEnable=NULL, TaskOnDisable aOnDisable=NULL);
+#define TASK_REQUEST_SET_5                  TASK_REQUEST_SET
+#define TASK_REQUEST_SET_4_DEFAULT          NULL
+#define TASK_REQUEST_SET_5_DEFAULT          NULL
+// void setInterval(unsigned long aInterval);
+#define TASK_REQUEST_SETINTERVAL_1          TASK_REQUEST_SETINTERVAL
+// void setIntervalNodelay(unsigned long aInterval, unsigned int aOption = TASK_INTERVAL_KEEP);
+#define TASK_REQUEST_SETINTERVALNODELAY_2   TASK_REQUEST_SETINTERVALNODELAY
+#define TASK_REQUEST_SETINTERVALNODELAY_2_DEFAULT   TASK_INTERVAL_KEEP
+// void setIterations(long aIterations);
+#define TASK_REQUEST_SETITERATIONS_1        TASK_REQUEST_SETITERATIONS
+// void setCallback(TaskCallback aCallback)
+#define TASK_REQUEST_SETCALLBACK_1          TASK_REQUEST_SETCALLBACK
+// setOnEnable(TaskOnEnable aCallback) ;
+#define TASK_REQUEST_SETONENABLE_1          TASK_REQUEST_SETONENABLE
+// setOnDisable(TaskOnDisable aCallback) ;
+#define TASK_REQUEST_SETONDISABLE_1         TASK_REQUEST_SETONDISABLE
+
+
+typedef struct {
+    _task_request_type_t req_type;
+    void*           object_ptr;
+    unsigned long   param1;
+    unsigned long   param2;
+    unsigned long   param3;
+    unsigned long   param4;
+    unsigned long   param5;
+} _task_request_t;
+
+#endif  //_TASK_THREAD_SAFE
+
 #ifdef _TASK_STATUS_REQUEST
 
 #define TASK_SR_OK          0
@@ -128,20 +307,20 @@ uint32_t external_micros();
 class StatusRequest {
   friend class Scheduler;
   public:
-    __INLINE StatusRequest();
-    __INLINE void setWaiting(unsigned int aCount = 1);
-    __INLINE bool __TASK_IRAM signal(int aStatus = 0);
-    __INLINE void __TASK_IRAM signalComplete(int aStatus = 0);
-    __INLINE bool pending();
-    __INLINE bool completed();
-    __INLINE int  getStatus();
-    __INLINE int  getCount();
+    _TASK_INLINE StatusRequest();
+    _TASK_INLINE void setWaiting(unsigned int aCount = 1);
+    _TASK_INLINE bool _TASK_IRAM signal(int aStatus = 0);
+    _TASK_INLINE void _TASK_IRAM signalComplete(int aStatus = 0);
+    _TASK_INLINE bool pending();
+    _TASK_INLINE bool completed();
+    _TASK_INLINE int  getStatus();
+    _TASK_INLINE int  getCount();
     
 #ifdef _TASK_TIMEOUT
-    __INLINE void setTimeout(unsigned long aTimeout) { iTimeout = aTimeout; };
-    __INLINE unsigned long getTimeout() { return iTimeout; };
-    __INLINE void resetTimeout();
-    __INLINE long untilTimeout();
+    _TASK_INLINE void setTimeout(unsigned long aTimeout) { iTimeout = aTimeout; };
+    _TASK_INLINE unsigned long getTimeout() { return iTimeout; };
+    _TASK_INLINE void resetTimeout();
+    _TASK_INLINE long untilTimeout();
 #endif
 
   _TASK_SCOPE:
@@ -189,7 +368,7 @@ typedef struct  {
 #ifdef _TASK_TIMEOUT
     bool  timeout       : 1;           // indication if task timed out
 #endif  //  _TASK_TIMEOUT
-} __task_status;
+} _task_status;
 
 
 class Task {
@@ -197,14 +376,14 @@ class Task {
   public:
 
 #ifdef _TASK_OO_CALLBACKS
-    __INLINE Task(unsigned long aInterval=0, long aIterations=0, Scheduler* aScheduler=NULL, bool aEnable=false
+    _TASK_INLINE Task(unsigned long aInterval=0, long aIterations=0, Scheduler* aScheduler=NULL, bool aEnable=false
 #ifdef _TASK_SELF_DESTRUCT
     , bool aSelfDestruct=false);
 #else
     );
 #endif  // #ifdef _TASK_SELF_DESTRUCT
 #else
-    __INLINE Task(unsigned long aInterval=0, long aIterations=0, TaskCallback aCallback=NULL, Scheduler* aScheduler=NULL, bool aEnable=false, TaskOnEnable aOnEnable=NULL, TaskOnDisable aOnDisable=NULL
+    _TASK_INLINE Task(unsigned long aInterval=0, long aIterations=0, TaskCallback aCallback=NULL, Scheduler* aScheduler=NULL, bool aEnable=false, TaskOnEnable aOnEnable=NULL, TaskOnDisable aOnDisable=NULL
 #ifdef _TASK_SELF_DESTRUCT
   , bool aSelfDestruct=false);
 #else
@@ -215,110 +394,109 @@ class Task {
 
 #ifdef _TASK_STATUS_REQUEST
 #ifdef _TASK_OO_CALLBACKS
-//    __INLINE Task(Scheduler* aScheduler=NULL);
-    __INLINE Task(Scheduler* aScheduler);
+//    _TASK_INLINE Task(Scheduler* aScheduler=NULL);
+    _TASK_INLINE Task(Scheduler* aScheduler);
 #else
-//    __INLINE Task(TaskCallback aCallback=NULL, Scheduler* aScheduler=NULL, TaskOnEnable aOnEnable=NULL, TaskOnDisable aOnDisable=NULL);
-    __INLINE Task(TaskCallback aCallback, Scheduler* aScheduler, TaskOnEnable aOnEnable=NULL, TaskOnDisable aOnDisable=NULL);
+//    _TASK_INLINE Task(TaskCallback aCallback=NULL, Scheduler* aScheduler=NULL, TaskOnEnable aOnEnable=NULL, TaskOnDisable aOnDisable=NULL);
+    _TASK_INLINE Task(TaskCallback aCallback, Scheduler* aScheduler, TaskOnEnable aOnEnable=NULL, TaskOnDisable aOnDisable=NULL);
 #endif // _TASK_OO_CALLBACKS
 #endif  // _TASK_STATUS_REQUEST
 
-    virtual __INLINE ~Task();
+    virtual _TASK_INLINE ~Task();
 
 #ifdef _TASK_TIMEOUT
-    __INLINE void setTimeout(unsigned long aTimeout, bool aReset=false);
-    __INLINE void resetTimeout();
-    __INLINE unsigned long getTimeout();
-    __INLINE long untilTimeout();
-    __INLINE bool timedOut();
+    _TASK_INLINE void setTimeout(unsigned long aTimeout, bool aReset=false);
+    _TASK_INLINE void resetTimeout();
+    _TASK_INLINE unsigned long getTimeout();
+    _TASK_INLINE long untilTimeout();
+    _TASK_INLINE bool timedOut();
 #endif
 
-    __INLINE bool __TASK_IRAM enable();
-    __INLINE bool __TASK_IRAM enableIfNot();
-    __INLINE bool __TASK_IRAM enableDelayed(unsigned long aDelay=0);
-    __INLINE bool __TASK_IRAM restart();
-    __INLINE bool __TASK_IRAM restartDelayed(unsigned long aDelay=0);
-
-    __INLINE void __TASK_IRAM delay(unsigned long aDelay=0);
-    __INLINE void adjust(long aInterval);
-    __INLINE void __TASK_IRAM forceNextIteration();
-    __INLINE bool disable();
-    __INLINE void abort();
-    __INLINE void cancel();
-    __INLINE bool isEnabled();
-    __INLINE bool canceled();
+    _TASK_INLINE bool _TASK_IRAM enable();
+    _TASK_INLINE bool _TASK_IRAM enableIfNot();
+    _TASK_INLINE bool _TASK_IRAM enableDelayed(unsigned long aDelay=0);
+    _TASK_INLINE bool _TASK_IRAM restart();
+    _TASK_INLINE bool _TASK_IRAM restartDelayed(unsigned long aDelay=0);
+
+    _TASK_INLINE void _TASK_IRAM delay(unsigned long aDelay=0);
+    _TASK_INLINE void adjust(long aInterval);
+    _TASK_INLINE void _TASK_IRAM forceNextIteration();
+    _TASK_INLINE bool _TASK_IRAM disable();
+    _TASK_INLINE void _TASK_IRAM abort();
+    _TASK_INLINE void _TASK_IRAM cancel();
+    _TASK_INLINE bool isEnabled();
+    _TASK_INLINE bool canceled();
 
 #ifdef _TASK_SCHEDULING_OPTIONS
-    __INLINE unsigned int getSchedulingOption() { return iOption; }
-    __INLINE void setSchedulingOption(unsigned int aOption) {  iOption = aOption; }
+    _TASK_INLINE unsigned int getSchedulingOption() { return iOption; }
+    _TASK_INLINE void setSchedulingOption(unsigned int aOption) {  iOption = aOption; }
 #endif  //_TASK_SCHEDULING_OPTIONS
 
 #ifdef _TASK_OO_CALLBACKS
-    __INLINE void set(unsigned long aInterval, long aIterations);
+    _TASK_INLINE void set(unsigned long aInterval, long aIterations);
 #else
-    __INLINE void set(unsigned long aInterval, long aIterations, TaskCallback aCallback,TaskOnEnable aOnEnable=NULL, TaskOnDisable aOnDisable=NULL);
+    _TASK_INLINE void set(unsigned long aInterval, long aIterations, TaskCallback aCallback,TaskOnEnable aOnEnable=NULL, TaskOnDisable aOnDisable=NULL);
 #endif // _TASK_OO_CALLBACKS
-    __INLINE void setInterval(unsigned long aInterval);
-    __INLINE void setIntervalNodelay(unsigned long aInterval, unsigned int aOption = TASK_INTERVAL_KEEP);
-    __INLINE unsigned long getInterval();
-    __INLINE void setIterations(long aIterations);
-    __INLINE long getIterations();
-    __INLINE unsigned long getRunCounter();
+    _TASK_INLINE void setInterval(unsigned long aInterval);
+    _TASK_INLINE void setIntervalNodelay(unsigned long aInterval, unsigned int aOption = TASK_INTERVAL_KEEP);
+    _TASK_INLINE unsigned long getInterval();
+    _TASK_INLINE void setIterations(long aIterations);
+    _TASK_INLINE long getIterations();
+    _TASK_INLINE unsigned long getRunCounter();
     
 #ifdef _TASK_SELF_DESTRUCT
-    __INLINE void setSelfDestruct(bool aSelfDestruct=true) { iStatus.selfdestruct = aSelfDestruct; }
-    __INLINE bool getSelfDestruct() { return iStatus.selfdestruct; }
+    _TASK_INLINE void setSelfDestruct(bool aSelfDestruct=true) { iStatus.selfdestruct = aSelfDestruct; }
+    _TASK_INLINE bool getSelfDestruct() { return iStatus.selfdestruct; }
 #endif  //  #ifdef _TASK_SELF_DESTRUCT
 
 #ifdef _TASK_OO_CALLBACKS
-    virtual __INLINE bool Callback() =0;  // return true if run was "productive - this will disable sleep on the idle run for next pass
-    virtual __INLINE bool OnEnable();  // return true if task should be enabled, false if it should remain disabled
-    virtual __INLINE void OnDisable();
+    virtual _TASK_INLINE bool Callback() =0;  // return true if run was "productive - this will disable sleep on the idle run for next pass
+    virtual _TASK_INLINE bool OnEnable();  // return true if task should be enabled, false if it should remain disabled
+    virtual _TASK_INLINE void OnDisable();
 #else
-    __INLINE void setCallback(TaskCallback aCallback) ;
-    __INLINE void setOnEnable(TaskOnEnable aCallback) ;
-    __INLINE void setOnDisable(TaskOnDisable aCallback) ;
-    __INLINE void yield(TaskCallback aCallback);
-    __INLINE void yieldOnce(TaskCallback aCallback);
+    _TASK_INLINE void setCallback(TaskCallback aCallback) ;
+    _TASK_INLINE void setOnEnable(TaskOnEnable aCallback) ;
+    _TASK_INLINE void setOnDisable(TaskOnDisable aCallback) ;
+    _TASK_INLINE void yield(TaskCallback aCallback);
+    _TASK_INLINE void yieldOnce(TaskCallback aCallback);
 #endif // _TASK_OO_CALLBACKS
 
-    __INLINE bool isFirstIteration() ;
-    __INLINE bool isLastIteration() ;
+    _TASK_INLINE bool isFirstIteration() ;
+    _TASK_INLINE bool isLastIteration() ;
 
 #ifdef _TASK_TIMECRITICAL
-    __INLINE long getOverrun() ;
-    __INLINE long getStartDelay() ;
+    _TASK_INLINE long getOverrun() ;
+    _TASK_INLINE long getStartDelay() ;
 #endif  // _TASK_TIMECRITICAL
 
 #ifdef _TASK_STATUS_REQUEST
-    __INLINE bool waitFor(StatusRequest* aStatusRequest, unsigned long aInterval = 0, long aIterations = 1);
-    __INLINE bool waitForDelayed(StatusRequest* aStatusRequest, unsigned long aInterval = 0, long aIterations = 1);
-    __INLINE StatusRequest* getStatusRequest() ;
-    __INLINE StatusRequest* getInternalStatusRequest() ;
+    _TASK_INLINE bool waitFor(StatusRequest* aStatusRequest, unsigned long aInterval = 0, long aIterations = 1);
+    _TASK_INLINE bool waitForDelayed(StatusRequest* aStatusRequest, unsigned long aInterval = 0, long aIterations = 1);
+    _TASK_INLINE StatusRequest* getStatusRequest() ;
+    _TASK_INLINE StatusRequest* getInternalStatusRequest() ;
 #endif  // _TASK_STATUS_REQUEST
 
 #ifdef _TASK_WDT_IDS
-    __INLINE void setId(unsigned int aID) ;
-    __INLINE unsigned int getId() ;
-    __INLINE void setControlPoint(unsigned int aPoint) ;
-    __INLINE unsigned int getControlPoint() ;
+    _TASK_INLINE void setId(unsigned int aID) ;
+    _TASK_INLINE unsigned int getId() ;
+    _TASK_INLINE void setControlPoint(unsigned int aPoint) ;
+    _TASK_INLINE unsigned int getControlPoint() ;
 #endif  // _TASK_WDT_IDS
 
 #ifdef _TASK_LTS_POINTER
-    __INLINE void  setLtsPointer(void *aPtr) ;
-    __INLINE void* getLtsPointer() ;
+    _TASK_INLINE void  setLtsPointer(void *aPtr) ;
+    _TASK_INLINE void* getLtsPointer() ;
 #endif  // _TASK_LTS_POINTER
 
 #ifdef _TASK_EXPOSE_CHAIN
-    __INLINE Task*  getPreviousTask() { return iPrev; };  // pointer to the previous task in the chain, NULL if first or not set
-    __INLINE Task*  getNextTask()     { return iNext; };  // pointer to the next task in the chain, NULL if last or not set
+    _TASK_INLINE Task*  getPreviousTask() { return iPrev; };  // pointer to the previous task in the chain, NULL if first or not set
+    _TASK_INLINE Task*  getNextTask()     { return iNext; };  // pointer to the next task in the chain, NULL if last or not set
 #endif // _TASK_EXPOSE_CHAIN
 
-
   _TASK_SCOPE:
-    __INLINE void reset();
+    _TASK_INLINE void reset();
 
-    volatile __task_status    iStatus;
+    volatile _task_status     iStatus;
     volatile unsigned long    iInterval;             // execution interval in milliseconds (or microseconds). 0 - immediate
     volatile unsigned long    iDelay;                // actual delay until next execution (usually equal iInterval)
     volatile unsigned long    iPreviousMillis;       // previous invocation time (millis).  Next invocation = iPreviousMillis + iInterval.  Delayed tasks will "catch up"
@@ -363,76 +541,78 @@ class Task {
     unsigned long            iTimeout;               // Task overall timeout
     unsigned long            iStarttime;             // millis at task start time
 #endif // _TASK_TIMEOUT
-
-
-#ifdef _TASK_THREAD_SAFE
-    volatile uint8_t          iMutex;                // a mutex to pause scheduling during chages to the task
-#endif
 };
 
 class Scheduler {
   friend class Task;
   public:
-    __INLINE Scheduler();
+    _TASK_INLINE Scheduler();
 //  ~Scheduler();
-    __INLINE void init();
-    __INLINE void addTask(Task& aTask);
-    __INLINE void deleteTask(Task& aTask);
-    __INLINE void pause() { iPaused = true; };
-    __INLINE void resume() { iPaused = false; };
-    __INLINE void enable() { iEnabled = true; };
-    __INLINE void disable() { iEnabled = false; };
+    _TASK_INLINE void init();
+    _TASK_INLINE void addTask(Task& aTask);
+    _TASK_INLINE void deleteTask(Task& aTask);
+    _TASK_INLINE void pause() { iPaused = true; };
+    _TASK_INLINE void resume() { iPaused = false; };
+    _TASK_INLINE void enable() { iEnabled = true; };
+    _TASK_INLINE void disable() { iEnabled = false; };
 #ifdef _TASK_PRIORITY
-    __INLINE void disableAll(bool aRecursive = true);
-    __INLINE void enableAll(bool aRecursive = true);
-    __INLINE void startNow(bool aRecursive = true);       // reset ALL active tasks to immediate execution NOW.
+    _TASK_INLINE void disableAll(bool aRecursive = true);
+    _TASK_INLINE void enableAll(bool aRecursive = true);
+    _TASK_INLINE void startNow(bool aRecursive = true);       // reset ALL active tasks to immediate execution NOW.
 #else
-    __INLINE void disableAll();
-    __INLINE void enableAll();
-    __INLINE void startNow();                             // reset ALL active tasks to immediate execution NOW.
+    _TASK_INLINE void disableAll();
+    _TASK_INLINE void enableAll();
+    _TASK_INLINE void startNow();                             // reset ALL active tasks to immediate execution NOW.
 #endif
 
-    __INLINE bool execute();                              // Returns true if none of the tasks' callback methods was invoked (true = idle run)
+    _TASK_INLINE bool execute();                              // Returns true if none of the tasks' callback methods was invoked (true = idle run)
 
-    __INLINE Task& currentTask() ;                        // DEPRICATED
-    __INLINE Task* getCurrentTask() ;                     // Returns pointer to the currently active task
-    __INLINE long timeUntilNextIteration(Task& aTask);    // return number of ms until next iteration of a given Task
+    _TASK_INLINE Task& currentTask() ;                        // DEPRICATED
+    _TASK_INLINE Task* getCurrentTask() ;                     // Returns pointer to the currently active task
+    _TASK_INLINE long timeUntilNextIteration(Task& aTask);    // return number of ms until next iteration of a given Task
 
-    __INLINE unsigned long getActiveTasks() { return iActiveTasks; }
-    __INLINE unsigned long getTotalTasks() { return iTotalTasks; }
-    __INLINE unsigned long getInvokedTasks() { return iInvokedTasks; }
+    _TASK_INLINE unsigned long getActiveTasks() { return iActiveTasks; }
+    _TASK_INLINE unsigned long getTotalTasks() { return iTotalTasks; }
+    _TASK_INLINE unsigned long getInvokedTasks() { return iInvokedTasks; }
 #ifdef _TASK_TICKLESS
-    __INLINE unsigned long getNextRun() { return iNextRun; }
+    _TASK_INLINE unsigned long getNextRun() { return iNextRun; }
 #endif
 
 #ifdef _TASK_SLEEP_ON_IDLE_RUN
-    __INLINE void allowSleep(bool aState = true);
-    __INLINE void setSleepMethod( SleepCallback aCallback );
+    _TASK_INLINE void allowSleep(bool aState = true);
+    _TASK_INLINE void setSleepMethod( SleepCallback aCallback );
 #endif  // _TASK_SLEEP_ON_IDLE_RUN
 
 #ifdef _TASK_LTS_POINTER
-    __INLINE void* currentLts();
+    _TASK_INLINE void* currentLts();
 #endif  // _TASK_LTS_POINTER
 
 #ifdef _TASK_TIMECRITICAL
-    __INLINE bool isOverrun();
-    __INLINE void cpuLoadReset();
-    __INLINE unsigned long getCpuLoadCycle(){ return iCPUCycle; };
-    __INLINE unsigned long getCpuLoadIdle() { return iCPUIdle; };
-    __INLINE unsigned long getCpuLoadTotal();
+    _TASK_INLINE bool isOverrun();
+    _TASK_INLINE void cpuLoadReset();
+    _TASK_INLINE unsigned long getCpuLoadCycle(){ return iCPUCycle; };
+    _TASK_INLINE unsigned long getCpuLoadIdle() { return iCPUIdle; };
+    _TASK_INLINE unsigned long getCpuLoadTotal();
 #endif  // _TASK_TIMECRITICAL
 
 #ifdef _TASK_PRIORITY
-    __INLINE void setHighPriorityScheduler(Scheduler* aScheduler);
-    __INLINE static Scheduler& currentScheduler() { return *(iCurrentScheduler); };
+    _TASK_INLINE void setHighPriorityScheduler(Scheduler* aScheduler);
+    _TASK_INLINE static Scheduler& currentScheduler() { return *(iCurrentScheduler); };
 #endif  // _TASK_PRIORITY
 
 #ifdef _TASK_EXPOSE_CHAIN
-    __INLINE Task*  getFirstTask() { return iFirst; };       // pointer to the previous task in the chain, NULL if first or not set
-    __INLINE Task*  getLastTask()  { return iLast;  };       // pointer to the next task in the chain, NULL if last or not set
+    _TASK_INLINE Task*  getFirstTask() { return iFirst; };       // pointer to the previous task in the chain, NULL if first or not set
+    _TASK_INLINE Task*  getLastTask()  { return iLast;  };       // pointer to the next task in the chain, NULL if last or not set
 #endif // _TASK_EXPOSE_CHAIN
 
+#ifdef _TASK_THREAD_SAFE
+    _TASK_INLINE void   requestAction(_task_request_t* aRequest);    // this method places the request on the task request queue
+    _TASK_INLINE void   requestAction(void* aObject, _task_request_type_t aType, unsigned long aParam1, unsigned long aParam2, unsigned long aParam3, unsigned long aParam4, unsigned long aParam5);
+#endif
   _TASK_SCOPE:
+#ifdef _TASK_THREAD_SAFE
+    _TASK_INLINE void   processRequests();
+#endif
     Task          *iFirst, *iLast, *iCurrent;        // pointers to first, last and current tasks in the chain
 
     volatile bool iPaused, iEnabled;

+ 5 - 4
src/TaskSchedulerSleepMethods.h

@@ -56,10 +56,11 @@ extern unsigned long tStart, tFinish;
 const unsigned long tRem = 1000-_TASK_ESP32_DLY_THRESHOLD;
 
 void SleepMethod( unsigned long aDuration ) {
-    if ( aDuration < tRem ) {
-        esp_sleep_enable_timer_wakeup((uint64_t) (1000 - aDuration));
-        esp_light_sleep_start();
-    }
+    // === NOT IMPLEMENTED ===
+    // if ( aDuration < tRem ) {
+    //     esp_sleep_enable_timer_wakeup((uint64_t) (1000 - aDuration));
+    //     esp_light_sleep_start();
+    // }
 }
 // ARDUINO_ARCH_ESP32