├── .github └── workflows │ └── main.yml ├── README.md ├── examples ├── Ex_01_SayHello │ └── Ex_01_SayHello.ino └── Ex_02_MultiBlink │ └── Ex_02_MultiBlink.ino ├── keywords.txt ├── library.properties └── src ├── ProcessScheduler.h └── ProcessScheduler ├── Config.h ├── Includes.h ├── Process.cpp ├── Process.h ├── Scheduler.cpp └── Scheduler.h /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: PlatformIO CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | example: [examples/Ex_01_SayHello/Ex_01_SayHello.ino, examples/Ex_02_MultiBlink/Ex_02_MultiBlink.ino] 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Cache pip 16 | uses: actions/cache@v2 17 | with: 18 | path: ~/.cache/pip 19 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} 20 | restore-keys: ${{ runner.os }}-pip- 21 | - name: Cache PlatformIO 22 | uses: actions/cache@v2 23 | with: 24 | path: ~/.platformio 25 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} 26 | - name: Set up Python 27 | uses: actions/setup-python@v2 28 | - name: Install PlatformIO 29 | run: | 30 | python -m pip install --upgrade pip 31 | pip install --upgrade platformio 32 | - name: Install library dependencies 33 | run: pio lib -g install 667 34 | - name: Run PlatformIO 35 | run: pio ci --lib="." --board=uno --board=d1_mini 36 | env: 37 | PLATFORMIO_CI_SRC: ${{ matrix.example }} 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ArduinoProcessScheduler 2 | A cooperative Arduino object oriented, cooperative process scheduler to replace them all. 3 | 4 | ## What is this? 5 | As your Arduino projects get more complicated, you will begin to see the need for multitasking, or at least appear to multitask. Perhaps you want to check if a button was pressed as often as you can, but you only want to update a display once every second. Trying to do this on your own can quickly turn into overwhelming spagetti code involving `millis()`. `ArduinoProcessScheduler` seeks to simplify this. Simply create your custom Process that needs to be serviced at certain times, and let the scheduler handle the rest. 6 | 7 | ## Why this one? 8 | 9 | Here are some similar popular libraries that inspired this one: 10 | - [TaskScheduler](https://github.com/arkhipenko/TaskScheduler) 11 | - [Scheduler](https://github.com/arduino-libraries/Scheduler) 12 | - [Arduino-Scheduler](https://github.com/mikaelpatel/Arduino-Scheduler) 13 | 14 | ### What is wrong with them? 15 | 16 | 1. They all treat processes/tasks as just callback functions. 17 | 1. Forces you to use ugly global and/or static function variables to track process state. 18 | 2. Limits you to one instance of a process, or lots of copy & paste. 19 | 3. Impossible to truly dynamically create new processes, you are really just enabling/disabling a callback function. 20 | 2. Preemptive schedulers must split stack between all processes 21 | 1. With 2K or RAM and 8 processes, preemptive scheduler could at most equally give each Process 2k/8 = 256 Bytes of RAM. 22 | 3. No concurrency protection (not interrupt safe) 23 | 1. What if an interrupt fires an tries to disable a process while it is running? 24 | 25 | 26 | ## Features 27 | ### Basic 28 | - Control over how often a process runs (periodically, iterations, or as often as possible) 29 | - Process priority levels (easily make custom levels as well) 30 | - Dynamically add/remove and enable/disable processes 31 | - Interrupt safe (add, disable, destroy, etc.. processes from interrupt routines) 32 | - Process concurrency protection (Process will always be in a valid state) 33 | 34 | ### Advanced 35 | - Spawn new processes from within running processes 36 | - Automatic process monitoring statistics (calculates % CPU time for process) 37 | - Truly object oriented (a Process is its own object) 38 | - Exception handling (wait what?!) 39 | - Scheduler can automatically interrupt stuck processes 40 | 41 | ## Supported Platfroms 42 | - AVR 43 | - ESP8266 (No exception handling or process timeouts) 44 | 45 | 46 | ## Install & Usage 47 | See [Wiki](https://github.com/wizard97/ArduinoProcessScheduler/wiki) 48 | 49 | NOTE: Don't forget to install this [RingBuf](https://github.com/wizard97/ArduinoRingBuffer) library dependency! 50 | 51 | ## Contributing 52 | I welcome any contributions! Here are some ideas: 53 | - Built in logging 54 | - Built in timers 55 | - Built in process ownership (Library tracks who owns a Process) 56 | - More advanced Process statistics monitoring 57 | - Adding support for additional platforms 58 | - Any Examples! 59 | 60 | 61 | ## License 62 | MIT. 63 | -------------------------------------------------------------------------------- /examples/Ex_01_SayHello/Ex_01_SayHello.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Example 01: Ex_01_SayHello.ino 3 | * By: D. Aaron Wisner 4 | * 5 | * In this example we are creating a "SayHelloProcess" Class that prints "Hello from Process: 'id'" at a constant period 6 | */ 7 | 8 | #include 9 | 10 | // Create my custom process SayHelloProcess 11 | class SayHelloProcess : public Process 12 | { 13 | public: 14 | // Call the Process constructor 15 | SayHelloProcess(Scheduler &manager, ProcPriority pr, unsigned int period, int iterations) 16 | : Process(manager, pr, period, iterations) {} 17 | 18 | protected: 19 | // Create our service routine 20 | virtual void service() 21 | { 22 | Serial.print("Hello from Process: "); 23 | Serial.println(getID()); 24 | } 25 | }; 26 | 27 | Scheduler sched; // Create a global Scheduler object 28 | 29 | // Create our high priority process that will get serviced as often as possible and run forever 30 | SayHelloProcess myProc(sched, HIGH_PRIORITY, SERVICE_CONSTANTLY, RUNTIME_FOREVER); 31 | 32 | void setup() 33 | { 34 | Serial.begin(9600); 35 | // Add our process to the scheduler 36 | myProc.add(); 37 | //enable it 38 | myProc.enable(); 39 | } 40 | 41 | void loop() 42 | { 43 | sched.run(); 44 | } 45 | -------------------------------------------------------------------------------- /examples/Ex_02_MultiBlink/Ex_02_MultiBlink.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Example 02: Ex_02_MultiBlink.ino 3 | * By: D. Aaron Wisner 4 | * 5 | * In this example we are creating a "BlinkProcess" Class that will toggle a pin at a constant period 6 | * We create 3 BlinkProcess objects that will blink at a diff. period and add them to the scheduler: 7 | * - blink250 (pin 13) 8 | * - blink500 (pin 12) 9 | * - blink1000 (pin 11) 10 | * Connect an LED to each of these pins and watch them blink 11 | */ 12 | 13 | #include 14 | 15 | // Create my custom Blink Process 16 | class BlinkProcess : public Process 17 | { 18 | public: 19 | // Call the Process constructor 20 | BlinkProcess(Scheduler &manager, ProcPriority pr, unsigned int period, int pin) 21 | : Process(manager, pr, period) 22 | { 23 | _pinState = LOW; // Set the default state 24 | _pin = pin; // Store the pin number 25 | } 26 | 27 | protected: 28 | //setup the pins 29 | virtual void setup() 30 | { 31 | pinMode(_pin, OUTPUT); 32 | _pinState = LOW; 33 | digitalWrite(_pin, _pinState); 34 | } 35 | 36 | // Undo setup() 37 | virtual void cleanup() 38 | { 39 | pinMode(_pin, INPUT); 40 | _pinState = LOW; 41 | } 42 | 43 | //LEDs should be off when disabled 44 | virtual void onDisable() 45 | { 46 | _pinState = LOW; 47 | digitalWrite(_pin, _pinState); 48 | } 49 | 50 | //Start the LEDs on 51 | virtual void onEnable() 52 | { 53 | _pinState = HIGH; 54 | digitalWrite(_pin, _pinState); 55 | } 56 | 57 | // Create our service routine 58 | virtual void service() 59 | { 60 | // If pin is on turn it off, otherwise turn it on 61 | _pinState = !_pinState; 62 | digitalWrite(_pin, _pinState); 63 | } 64 | 65 | private: 66 | bool _pinState; //the Current state of the pin 67 | int _pin; // The pin the LED is on 68 | }; 69 | 70 | Scheduler sched; // Create a global Scheduler object 71 | 72 | // Create our blink processes 73 | BlinkProcess blink250(sched, HIGH_PRIORITY, 250, 13); // Blink 13 every 250 ms 74 | BlinkProcess blink500(sched, HIGH_PRIORITY, 500, 12); // Blink 12 every 500 ms 75 | BlinkProcess blink1000(sched, HIGH_PRIORITY, 1000, 11); // Blink 11 every 1000 ms 76 | 77 | void setup() 78 | { 79 | // Add and enable our blink processes 80 | blink250.add(true); // Same as calling blink250.add() and blink250.enable(); 81 | blink500.add(true); 82 | blink1000.add(true); 83 | } 84 | 85 | void loop() 86 | { 87 | sched.run(); 88 | } 89 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | Scheduler KEYWORD1 2 | Process KEYWORD1 3 | 4 | add KEYWORD2 5 | disable KEYWORD2 6 | enable KEYWORD2 7 | destroy KEYWORD2 8 | restart KEYWORD2 9 | getID KEYWORD2 10 | isEnabled KEYWORD2 11 | isNotDestroyed KEYWORD2 12 | scheduler KEYWORD2 13 | getIterations KEYWORD2 14 | getPeriod KEYWORD2 15 | getPriority KEYWORD2 16 | timeToNextRun KEYWORD2 17 | getActualRunTS KEYWORD2 18 | getScheduledTS KEYWORD2 19 | getOverSchedThresh KEYWORD2 20 | getCurrPBehind KEYWORD2 21 | setIterations KEYWORD2 22 | setPeriod KEYWORD2 23 | force KEYWORD2 24 | resetOverSchedWarning KEYWORD2 25 | resetTimeStamps KEYWORD2 26 | getAvgRunTime KEYWORD2 27 | getLoadPercent KEYWORD2 28 | getTimeout KEYWORD2 29 | setTimeout KEYWORD2 30 | yield KEYWORD2 31 | timeToTimeout KEYWORD2 32 | getStartDelay KEYWORD2 33 | service KEYWORD2 34 | setup KEYWORD2 35 | cleanup KEYWORD2 36 | onEnable KEYWORD2 37 | onDisable KEYWORD2 38 | handleWarning KEYWORD2 39 | raiseException KEYWORD2 40 | handleException KEYWORD2 41 | 42 | halt KEYWORD2 43 | getActive KEYWORD2 44 | findProcById KEYWORD2 45 | countProcesses KEYWORD2 46 | getCurrTS KEYWORD2 47 | run KEYWORD2 48 | updateStats KEYWORD2 49 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=ProcessScheduler 2 | version=1.0 3 | author=D. Aaron Wisner 4 | maintainer=D. Aaron Wisner 5 | sentence=An OOP multitasking library 6 | paragraph=A thread safe Library that gives the user fine grained control over custom 'Process' objects. Dynamically schedule how often a process runs and/or number of iterations. Advanced features include Exception handling and Process CPU time statistics. 7 | category=Timing 8 | url=https://github.com/wizard97/ArduinoProcessScheduler 9 | architectures=avr,esp8266 10 | includes=ProcessScheduler.h 11 | depends=RingBuf 12 | -------------------------------------------------------------------------------- /src/ProcessScheduler.h: -------------------------------------------------------------------------------- 1 | #ifndef PROCESS_SCHEDULER_H 2 | #define PROCESS_SCHEDULER_H 3 | 4 | #include "ProcessScheduler/Process.h" 5 | #include "ProcessScheduler/Scheduler.h" 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /src/ProcessScheduler/Config.h: -------------------------------------------------------------------------------- 1 | #ifndef PS_CONFIG_H 2 | #define PS_CONFIG_H 3 | 4 | #include 5 | 6 | /* Uncomment this to allow Exception Handling functionality */ 7 | //#define _PROCESS_EXCEPTION_HANDLING 8 | 9 | /* Uncomment this to allow the scheduler to interrupt long running processes */ 10 | // This requires _PROCESS_EXCEPTION_HANDLING to also be enabled 11 | //#define _PROCESS_TIMEOUT_INTERRUPTS 12 | 13 | /* Uncomment this to allow Process timing statistics functionality */ 14 | //#define _PROCESS_STATISTICS 15 | 16 | /* Uncomment this to use microseconds instead of milliseconds for timestamp unit (more precise) */ 17 | //#define _MICROS_PRECISION 18 | 19 | 20 | /* The size of the scheduler job queue, */ 21 | //increase if add(), destroy(), enable(), disable(), or updateStats() is returning false*/ 22 | #define SCHEDULER_JOB_QUEUE_SIZE 20 23 | 24 | typedef enum ProcPriority 25 | { 26 | // Feel free to add custom priority levels in here 27 | ////////////// BEGIN ////////////////// 28 | HIGH_PRIORITY = 0, 29 | MEDIUM_PRIORITY, 30 | LOW_PRIORITY, 31 | 32 | ////////////// END ////////////////// 33 | 34 | NUM_PRIORITY_LEVELS 35 | } ProcPriority; 36 | 37 | 38 | #ifdef _PROCESS_STATISTICS 39 | /*** The larger the following two types are, the more accurate the statistics will be ****/ 40 | // Type used to track process iterations run count 41 | typedef uint32_t hIterCount_t; 42 | // Type used to track process total runtime 43 | typedef uint32_t hTimeCount_t; 44 | /**************************************/ 45 | 46 | // What to divide the two vars above when overflow is about to happen 47 | #define HISTORY_DIV_FACTOR 2 48 | #endif 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /src/ProcessScheduler/Includes.h: -------------------------------------------------------------------------------- 1 | #ifndef SCHEDULER_PROCESS_INCLUDES_H 2 | #define SCHEDULER_PROCESS_INCLUDES_H 3 | 4 | #include 5 | #include 6 | #include "Config.h" 7 | 8 | 9 | class Scheduler; 10 | class Process; 11 | 12 | typedef enum ProcessWarning 13 | { 14 | // This Process is scheduled to often 15 | WARNING_PROC_OVERSCHEDULED = 0, 16 | 17 | #ifdef _PROCESS_TIMEOUT_INTERRUPTS 18 | // The scheduler interrupted your process service routine because it was taking longer than timeout set 19 | // This will likley leave you Process in an unknown state, perhaps call restart() 20 | ERROR_PROC_TIMED_OUT 21 | #endif 22 | } ProcessWarning; 23 | 24 | // Process period 25 | #define SERVICE_CONSTANTLY 0 26 | #define SERVICE_SECONDLY 1000 27 | #define SERVICE_MINUTELY 60000 28 | #define SERVICE_HOURLY 3600000 29 | 30 | // Number of Processs 31 | #define RUNTIME_FOREVER -1 32 | #define RUNTIME_ONCE 1 33 | 34 | #define PROCESS_NO_TIMEOUT 0 35 | 36 | #define OVERSCHEDULED_NO_WARNING 0 37 | 38 | #define LONGJMP_ISR_CODE -1000 39 | #define LONGJMP_YIELD_CODE -1001 40 | 41 | #define ALL_PRIORITY_LEVELS -1 42 | 43 | #ifndef SCHEDULER_JOB_QUEUE_SIZE 44 | #define SCHEDULER_JOB_QUEUE_SIZE 20 45 | #endif 46 | 47 | #if defined(ARDUINO_ARCH_AVR) 48 | #include 49 | #include 50 | #define ATOMIC_START ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 51 | #define ATOMIC_END } 52 | 53 | #include 54 | #define HALT_PROCESSOR() \ 55 | do { noInterrupts(); sleep_enable(); sleep_cpu(); } while(0) 56 | 57 | #define ENABLE_SCHEDULER_ISR() \ 58 | do { OCR0A = 0xAA; TIMSK0 |= _BV(OCIE0A); } while(0) 59 | 60 | 61 | #define DISABLE_SCHEDULER_ISR() \ 62 | do { TIMSK0 &= ~_BV(OCIE0A); } while(0) 63 | 64 | 65 | #elif defined(ARDUINO_ARCH_ESP8266) 66 | #ifndef __STRINGIFY 67 | #define __STRINGIFY(a) #a 68 | #endif 69 | 70 | #ifndef xt_rsil 71 | #define xt_rsil(level) (__extension__({uint32_t state; __asm__ __volatile__("rsil %0," __STRINGIFY(level) : "=a" (state)); state;})) 72 | #endif 73 | 74 | #ifndef xt_wsr_ps 75 | #define xt_wsr_ps(state) __asm__ __volatile__("wsr %0,ps; isync" :: "a" (state) : "memory") 76 | #endif 77 | 78 | #define ATOMIC_START do { uint32_t _savedIS = xt_rsil(15) ; 79 | #define ATOMIC_END xt_wsr_ps(_savedIS) ;} while(0); 80 | 81 | #define HALT_PROCESSOR() \ 82 | ESP.deepSleep(0) 83 | 84 | // Not supported on ESP8266 85 | #define ENABLE_SCHEDULER_ISR() 86 | #define DISABLE_SCHEDULER_ISR() 87 | 88 | #else 89 | #error "This library only supports AVR and ESP8266 Boards." 90 | #endif 91 | 92 | 93 | #ifdef _MICROS_PRECISION 94 | #define TIMESTAMP() micros() 95 | #else 96 | #define TIMESTAMP() millis() 97 | #endif 98 | 99 | #if defined(_PROCESS_EXCEPTION_HANDLING) && defined(ARDUINO_ARCH_ESP8266) 100 | #error "'_PROCESS_EXCEPTION_HANDLING' is not supported on the ESP8266." 101 | #endif 102 | 103 | 104 | #if defined(_PROCESS_TIMEOUT_INTERRUPTS) && !defined(_PROCESS_EXCEPTION_HANDLING) 105 | #error "'_PROCESS_TIMEOUT_INTERRUPTS' requires enabling `_PROCESS_EXCEPTION_HANDLING`" 106 | #endif 107 | #endif 108 | -------------------------------------------------------------------------------- /src/ProcessScheduler/Process.cpp: -------------------------------------------------------------------------------- 1 | #include "Process.h" 2 | #include "Scheduler.h" 3 | 4 | /*********** PUBLIC *************/ 5 | Process::Process(Scheduler &scheduler, ProcPriority priority, uint32_t period, 6 | int iterations, uint16_t overSchedThresh) 7 | : _scheduler(scheduler), _pLevel(priority) 8 | { 9 | this->_enabled = false; 10 | this->_period = period; 11 | this->_iterations = iterations; 12 | this->_force = false; 13 | this->_overSchedThresh = overSchedThresh; 14 | resetTimeStamps(); 15 | 16 | #ifdef _PROCESS_TIMEOUT_INTERRUPTS 17 | setTimeout(PROCESS_NO_TIMEOUT); 18 | #endif 19 | } 20 | 21 | void Process::resetTimeStamps() 22 | { 23 | ATOMIC_START 24 | { 25 | this->_scheduledTS = _scheduler.getCurrTS(); 26 | this->_actualTS = _scheduler.getCurrTS(); 27 | this->_pBehind = 0; 28 | } 29 | ATOMIC_END 30 | } 31 | 32 | bool Process::disable() 33 | { 34 | return _scheduler.disable(*this); 35 | } 36 | 37 | 38 | bool Process::enable() 39 | { 40 | return _scheduler.enable(*this); 41 | } 42 | 43 | 44 | bool Process::destroy() 45 | { 46 | return _scheduler.destroy(*this); 47 | } 48 | 49 | bool Process::add(bool enableIfNot) 50 | { 51 | return _scheduler.add(*this, enableIfNot); 52 | } 53 | 54 | bool Process::restart() 55 | { 56 | return _scheduler.restart(*this); 57 | } 58 | 59 | 60 | bool Process::needsServicing(uint32_t start) 61 | { 62 | return (isEnabled() && 63 | (_force || 64 | ((getPeriod() == SERVICE_CONSTANTLY || timeToNextRun(start) <= 0) && 65 | (getIterations() == RUNTIME_FOREVER || getIterations() > 0)))); 66 | } 67 | 68 | void Process::setIterations(int iterations) 69 | { 70 | ATOMIC_START 71 | { 72 | _iterations = iterations; 73 | } 74 | ATOMIC_END 75 | } 76 | 77 | 78 | void Process::setPeriod(uint32_t period) 79 | { 80 | ATOMIC_START 81 | { 82 | _period = period; 83 | } 84 | ATOMIC_END 85 | } 86 | 87 | 88 | // both must need servicing 89 | Process *Process::runWhich(Process *p1, Process *p2) 90 | { 91 | // All things being equal pick yes 92 | 93 | // Compare forces 94 | if (p1->forceSet() || p2->forceSet()) 95 | return p1->forceSet() ? p1 : p2; 96 | 97 | // whichever one is more behind goes first 98 | return (p1->timeToNextRun() <= p2->timeToNextRun()) ? p1 : p2; 99 | 100 | } 101 | 102 | 103 | 104 | /*********** PROTECTED *************/ 105 | 106 | // Fired on creation/destroy 107 | void Process::setup() { return; } 108 | void Process::cleanup() { return; } 109 | //called on enable/disable 110 | void Process::onEnable() { return; } 111 | void Process::onDisable() { return; } 112 | void Process::handleWarning(ProcessWarning warning) 113 | { 114 | if (warning == WARNING_PROC_OVERSCHEDULED) 115 | resetOverSchedWarning(); 116 | 117 | #ifdef _PROCESS_TIMEOUT_INTERRUPTS 118 | else if (warning == ERROR_PROC_TIMED_OUT) 119 | restart(); // default by restarting it 120 | #endif 121 | } 122 | 123 | /*********** PRIVATE *************/ 124 | bool Process::isPBehind(uint32_t curr) 125 | { 126 | return (curr - getScheduledTS()) >= getPeriod(); 127 | } 128 | 129 | 130 | void Process::willService(uint32_t now) 131 | { 132 | if (!_force) 133 | { 134 | if (getPeriod() != SERVICE_CONSTANTLY) 135 | setScheduledTS(getScheduledTS() + getPeriod()); 136 | else 137 | setScheduledTS(now); 138 | } else { 139 | _force = false; 140 | } 141 | 142 | setActualTS(now); 143 | 144 | // Handle scheduler warning 145 | if (getOverSchedThresh() != OVERSCHEDULED_NO_WARNING && isPBehind(now)) { 146 | incrPBehind(); 147 | if (getCurrPBehind() >= getOverSchedThresh()) 148 | handleWarning(WARNING_PROC_OVERSCHEDULED); 149 | } else { 150 | resetOverSchedWarning(); 151 | } 152 | } 153 | 154 | 155 | // Return true if last if should disable 156 | bool Process::wasServiced(bool wasForced) 157 | { 158 | if (!wasForced && getIterations() > 0) { //Was an iteration 159 | decIterations(); 160 | 161 | if (getIterations() == 0) 162 | return true; 163 | } 164 | return false; 165 | } 166 | 167 | 168 | #ifdef _PROCESS_TIMEOUT_INTERRUPTS 169 | 170 | void Process::setTimeout(uint32_t timeout) 171 | { 172 | // Can not let interrupt happen 173 | ATOMIC_START 174 | { 175 | _timeout = timeout; 176 | } 177 | ATOMIC_END 178 | } 179 | 180 | 181 | #endif 182 | 183 | #ifdef _PROCESS_STATISTICS 184 | 185 | uint32_t Process::getAvgRunTime() 186 | { 187 | if (!_histIterations) 188 | return 0; 189 | 190 | return _histRunTime / _histIterations; 191 | } 192 | 193 | bool Process::statsWillOverflow(hIterCount_t iter, hTimeCount_t tm) 194 | { 195 | return (_histIterations > _histIterations + iter) || (_histRunTime > _histRunTime + tm); 196 | 197 | } 198 | 199 | void Process::divStats(uint8_t div) 200 | { 201 | ++_histIterations /= div; 202 | ++_histRunTime /= div; 203 | } 204 | 205 | #endif 206 | -------------------------------------------------------------------------------- /src/ProcessScheduler/Process.h: -------------------------------------------------------------------------------- 1 | #ifndef PROCESS_H 2 | #define PROCESS_H 3 | 4 | #include "Includes.h" 5 | #include "Scheduler.h" 6 | 7 | class Scheduler; 8 | 9 | class Process 10 | { 11 | friend class Scheduler; 12 | public: 13 | /* 14 | * @param manager: The scheduler overseeing this Process 15 | * @param priority: The priority of this Process defined in config.h 16 | * @param period: The period this process should be serviced at (SERVICE_CONSTANTLY = As often as possible) 17 | * @param iterations: Number of iterations this process should be serviced before being disabled (RUNTIME_FOREVER = infinite) 18 | * @param overSchedThresh: The periods behind this process can get, before a WARNING_PROC_OVERSCHEDULED is triggered 19 | * (OVERSCHEDULED_NO_WARNING = a warning will never be triggered) 20 | */ 21 | Process(Scheduler &manager, ProcPriority priority, uint32_t period, 22 | int iterations=RUNTIME_FOREVER, 23 | uint16_t overSchedThresh = OVERSCHEDULED_NO_WARNING); 24 | 25 | ///////////////////// PROCESS OPERATIONS ///////////////////////// 26 | // These are all the same as calling scheduler.method(process) 27 | // Ex: scheduler.add(process), see Scheduler.h for documentation 28 | bool add(bool enableIfNot=false); 29 | bool disable(); 30 | bool enable(); 31 | bool destroy(); 32 | bool restart(); 33 | 34 | 35 | /* 36 | * Give both processes that need to run p1 and p2 37 | * return the process that should run first 38 | * 39 | * @return: p1 or p2 40 | */ 41 | static Process *runWhich(Process *p1, Process *p2); 42 | 43 | ///////////////////// GETTERS ///////////////////////// 44 | 45 | // These methods are also the same as calling calling scheduler.method(process) 46 | // See the documentation in Scheduler.h 47 | inline uint8_t getID() { return _sid; }; 48 | inline bool isEnabled() { return _enabled; } 49 | inline bool isNotDestroyed() { return _scheduler.isNotDestroyed(*this); } 50 | 51 | 52 | /* 53 | * Get the scheduler that is overseeing this process 54 | * 55 | * @return: Refrence to Scheduler 56 | */ 57 | inline Scheduler &scheduler() { return _scheduler; } 58 | 59 | /* 60 | * Get the remaining iterations this Process will be serviced 61 | * 62 | * @return: Number of iterations or RUNTIME_FOREVER 63 | */ 64 | inline int getIterations() { return _iterations; } 65 | 66 | /* 67 | * Get the period this Process is scheduled to run 68 | * 69 | * @return: period or SERVICE_CONSTANTLY 70 | */ 71 | inline uint32_t getPeriod() { return _period; } 72 | 73 | /* 74 | * Get the priority for this process 75 | * 76 | * @return: ProcPriority level defined in Config.h 77 | */ 78 | inline ProcPriority getPriority() { return _pLevel; } 79 | 80 | 81 | /* 82 | * Get the time before this process is scheduled to be serviced again 83 | * A negative number means the scheduler is behind 84 | * 85 | * @return: int32_t time offset 86 | */ 87 | inline int32_t timeToNextRun() { return timeToNextRun(_scheduler.getCurrTS()); } 88 | 89 | inline int32_t timeToNextRun(uint32_t curr) { return (int32_t)((_scheduledTS + _period) - curr); } 90 | 91 | 92 | /* 93 | * Get the timestamp the most recent iteration actually started at 94 | * 95 | * @return: uint32_t timestamp 96 | */ 97 | inline uint32_t getActualRunTS() { return _actualTS; } 98 | 99 | 100 | /* 101 | * Get the time stamp the most recent iteration was scheduled to run at 102 | * 103 | * @return: uint32_t timestamp 104 | */ 105 | inline uint32_t getScheduledTS() { return _scheduledTS; } 106 | 107 | 108 | /* 109 | * The number of additional periods behind since resetOverSchedWarning() was called 110 | * this Process can be before an WARNING_PROC_OVERSCHEDULED is triggered 111 | * 112 | * @return: uint16_t threshhold 113 | */ 114 | inline uint16_t getOverSchedThresh() { return _overSchedThresh; } 115 | 116 | 117 | /* 118 | * The number of additional periods behind since resetOverSchedWarning() was called 119 | * 120 | * @return: uint16_t count 121 | */ 122 | inline uint16_t getCurrPBehind() { return _pBehind; } 123 | 124 | 125 | ///////////////////// SETTERS ///////////////////////// 126 | 127 | /* 128 | * Set the number of iterations for this process to run 129 | * NOTE: Setting iterations to RUNTIME_FOREVER runs the process forever 130 | */ 131 | void setIterations(int iterations); 132 | 133 | /* 134 | * Set the period between when this process is serviced 135 | * NOTE: Setting period to SERVICE_CONSTANTLY, will have the Scheduler service it as often as possible 136 | */ 137 | void setPeriod(uint32_t period); 138 | 139 | /* 140 | * Force the scheduler to service this on the next pass (if enabled) 141 | * NOTE: This service will not count twoards an iteration 142 | */ 143 | inline void force() { _force = true; } 144 | 145 | /* 146 | * Reset the number of period behind count back to zero 147 | * ie. Reset the warning 148 | * NOTE: This warning will trigger again soon unless the scheduler can catch up 149 | */ 150 | inline void resetOverSchedWarning() { _pBehind = 0; } 151 | 152 | /* 153 | * Similar to resetOverSchedWarning(), except it also resets how far behind the scheduler is 154 | * Ex: If this the next iteration should of happened 20 ms ago, it will now be zero 155 | */ 156 | void resetTimeStamps(); 157 | 158 | // Enable this option in config.h to track time statistics on processes 159 | #ifdef _PROCESS_STATISTICS 160 | /* 161 | * Returns the average run time for this Process' service routine 162 | * 163 | * @return: uin32_t time 164 | */ 165 | uint32_t getAvgRunTime(); 166 | 167 | /* 168 | * Returns the computed CPU load % for this process 169 | * NOTE: You must call updateStats() on the scheduler to update this value 170 | * 171 | * @return: uint8_t percent 172 | */ 173 | inline uint8_t getLoadPercent() { return _histLoadPercent; } 174 | 175 | #endif 176 | 177 | 178 | // Enable this option in config.h to allow the Scheduler to interrupt processes that are not returning for their service routine 179 | #ifdef _PROCESS_TIMEOUT_INTERRUPTS 180 | /* 181 | * Returns the max runtime for this process before the Scheduler interrupts it 182 | * 183 | * @return: uint32_t timespan or PROCESS_NO_TIMEOUT 184 | */ 185 | inline uint32_t getTimeout() {return _timeout;} 186 | 187 | 188 | /* 189 | * Set the max runtime for this process before the Scheduler interrupts it 190 | */ 191 | void setTimeout(uint32_t timeout); 192 | #endif 193 | 194 | protected: 195 | 196 | #ifdef _PROCESS_EXCEPTION_HANDLING 197 | 198 | /* 199 | * Yield. This will immediatley transfer control back to the Scheduler 200 | * NOTE: that nothing below this call will ever be executed 201 | * NOTE: ONLY CALL THIS FROM WITHIN YOUR SERVICE ROUTINE 202 | */ 203 | inline void yield() { _scheduler.raiseException(LONGJMP_YIELD_CODE); } 204 | 205 | #endif 206 | 207 | ///////////////////// GETTERS ///////////////////////// 208 | #ifdef _PROCESS_TIMEOUT_INTERRUPTS 209 | /* 210 | * Returns the time remaining this process can run before it will be interrupted 211 | * NOTE: This method is undefined if the timeout is set to PROCESS_NO_TIMEOUT 212 | * 213 | * @return: int32_t time remaining, a negative value means it will happen any time now 214 | */ 215 | inline int32_t timeToTimeout() { return _timeout - (_scheduler.getCurrTS() - getActualRunTS()); } 216 | #endif 217 | 218 | /* 219 | * Get the delay from when the Scheduler scheduled it to run, to when it actualy was serviced 220 | * ie. Reset the warning 221 | */ 222 | inline uint32_t getStartDelay() { return _actualTS - _scheduledTS; } 223 | 224 | 225 | ///////////////////// VIRTUAL FUNCTIONS ///////////////////////// 226 | /* 227 | * This is the heart of your Process, this is the Scheduler's entry point into your Process' service routine 228 | * This is the method that will be called by the Scheduler 229 | * This method should run your service routine 230 | */ 231 | virtual void service() = 0; 232 | 233 | 234 | /////// THESE ARE ALL METHODS FOR YOU TO OVVERRIDE AS NEEDED //////// 235 | 236 | /* 237 | * This will be called by the scheduler when your process is added 238 | * Use it to setup everything this process uses 239 | */ 240 | virtual void setup(); 241 | 242 | 243 | /* 244 | * This is the opposite of setup(), it should undo everything it did 245 | * It will be called when this process is being destroyed 246 | */ 247 | virtual void cleanup(); 248 | 249 | 250 | /* 251 | * This will be called be the scheduler when this process is enabled 252 | */ 253 | virtual void onEnable(); 254 | 255 | 256 | /* 257 | * This is the opposite of onEnable(), it should undo everything it did 258 | * It will be called when this process is being disabled 259 | */ 260 | virtual void onDisable(); 261 | 262 | 263 | /* 264 | * This is the warning/error handler for your Process 265 | * The scheduler will call this method immediatley after an error/warning condition occured in this process 266 | * Look at Includes.h for the different types of ProcessWarnings 267 | */ 268 | virtual void handleWarning(ProcessWarning warning); 269 | 270 | 271 | #ifdef _PROCESS_EXCEPTION_HANDLING 272 | 273 | /* 274 | * Calling this method will raise an Exception and immediatley 275 | * call your handleException method 276 | * This is useful if you need to jump out of an error condition in a deeply nested function call 277 | * NOTE: You might find it useful to store more detailed info about the 278 | * error condition in class attributes 279 | */ 280 | virtual void raiseException(int e) { _scheduler.raiseException(e); } 281 | 282 | /* 283 | * This is the Exception handler for your Process' Service routine 284 | * If you raise an Exception, this method will immediatley be called 285 | * NOTE: The scheduler will go on to service the next process, 286 | * YOU WILL NOT GO BACK TO WHERE THE EXCEPTION WAS THROWN 287 | * 288 | * @return: Return true to catch the exception, otherwise the Exception will be passed down to 289 | * the handleException() method of the scheduler 290 | */ 291 | virtual bool handleException(int e) { return false; } 292 | 293 | #endif 294 | 295 | 296 | ////////////// YOU CAN IGNORE THE PRIVATE STUFF BELOW THIS LINE ////////////// 297 | private: 298 | // Return true if this Process needs servicing 299 | bool needsServicing(uint32_t start); 300 | // Called right before scheduler services 301 | void willService(uint32_t now); 302 | // Called right after scheduler services 303 | bool wasServiced(bool wasForced); 304 | // Returns true if this Process is over a period behind 305 | bool isPBehind(uint32_t curr); 306 | 307 | inline bool hasNext() { return _next; } 308 | // GETTERS 309 | inline Process *getNext() { return _next; } 310 | // SETTERS 311 | inline void setNext(Process *next) { this->_next = next; } 312 | inline void setID(uint8_t sid) { this->_sid = sid; } 313 | inline void decIterations() { _iterations--; } 314 | inline void setScheduledTS(uint32_t ts) { _scheduledTS = ts; } 315 | inline void setActualTS(uint32_t ts) { _actualTS = ts; } 316 | inline bool forceSet() { return _force; } 317 | 318 | inline void incrPBehind() { _pBehind++; } 319 | 320 | inline void setDisabled() { _enabled = false; } 321 | inline void setEnabled() { _enabled = true; } 322 | 323 | Scheduler &_scheduler; 324 | bool _enabled, _force; 325 | int _iterations; 326 | uint32_t _period; 327 | uint8_t _sid; 328 | uint32_t _scheduledTS, _actualTS; 329 | // Linked List 330 | Process *volatile _next; 331 | 332 | // Tracks overscheduled 333 | uint16_t _overSchedThresh, _pBehind; 334 | 335 | const ProcPriority _pLevel; 336 | 337 | 338 | #ifdef _PROCESS_TIMEOUT_INTERRUPTS 339 | uint32_t _timeout; 340 | #endif 341 | 342 | #ifdef _PROCESS_STATISTICS 343 | bool statsWillOverflow(hIterCount_t iter, hTimeCount_t tm); 344 | void divStats(uint8_t div); 345 | inline void setHistIterations(hIterCount_t val) { _histIterations = val; } 346 | inline void setHistRuntime(hTimeCount_t val) { _histRunTime = val; } 347 | inline void setHistLoadPercent(uint8_t percent) { _histLoadPercent = percent; } 348 | inline hIterCount_t getHistIterations() { return _histIterations; } 349 | inline hTimeCount_t getHistRunTime() { return _histRunTime; } 350 | 351 | hIterCount_t _histIterations; 352 | hTimeCount_t _histRunTime; 353 | uint8_t _histLoadPercent; 354 | 355 | #endif 356 | 357 | 358 | }; 359 | 360 | 361 | 362 | #endif 363 | -------------------------------------------------------------------------------- /src/ProcessScheduler/Scheduler.cpp: -------------------------------------------------------------------------------- 1 | #include "Scheduler.h" 2 | #include "Process.h" 3 | 4 | Process *Scheduler::_active = NULL; 5 | 6 | #ifdef _PROCESS_EXCEPTION_HANDLING 7 | jmp_buf Scheduler::_env = {}; 8 | #endif 9 | 10 | #ifdef _PROCESS_TIMEOUT_INTERRUPTS 11 | ISR(TIMER0_COMPA_vect) 12 | { 13 | if (Scheduler::getActive()) { // routine is running 14 | uint32_t timeout = Scheduler::getActive()->getTimeout(); 15 | if (timeout != PROCESS_NO_TIMEOUT && Scheduler::getCurrTS() - Scheduler::getActive()->getActualRunTS() >= timeout) 16 | longjmp(Scheduler::_env, LONGJMP_ISR_CODE); 17 | } 18 | } 19 | #endif 20 | 21 | Scheduler::Scheduler() 22 | : _pLevels{} 23 | { 24 | _lastID = 0; 25 | // Create queue 26 | _queue = RingBuf_new(sizeof(QueableOperation), SCHEDULER_JOB_QUEUE_SIZE); 27 | } 28 | 29 | Scheduler::~Scheduler() 30 | { 31 | processQueue(); 32 | RingBuf_delete(_queue); 33 | } 34 | 35 | uint32_t Scheduler::getCurrTS() 36 | { 37 | return TIMESTAMP(); 38 | } 39 | 40 | Process *Scheduler::getActive() 41 | { 42 | return _active; 43 | } 44 | 45 | bool Scheduler::isRunningProcess(Process &process) 46 | { 47 | return _active && &process == _active; 48 | } 49 | 50 | Process *Scheduler::findProcById(uint8_t id) 51 | { 52 | for (uint8_t i=0; i < NUM_PRIORITY_LEVELS; i++) 53 | { 54 | for (Process *serv = _pLevels[i].head; serv != NULL; serv = serv->getNext()) 55 | { 56 | if (serv->getID() == id) 57 | return serv; 58 | } 59 | } 60 | return NULL; 61 | } 62 | 63 | bool Scheduler::isNotDestroyed(Process &process) 64 | { 65 | return findNode(process); 66 | } 67 | 68 | bool Scheduler::isEnabled(Process &process) 69 | { 70 | return process.isEnabled(); 71 | } 72 | 73 | bool Scheduler::disable(Process &process) 74 | { 75 | QueableOperation op(&process, QueableOperation::DISABLE_SERVICE); 76 | return op.queue(_queue); 77 | } 78 | 79 | 80 | bool Scheduler::enable(Process &process) 81 | { 82 | QueableOperation op(&process, QueableOperation::ENABLE_SERVICE); 83 | return op.queue(_queue); 84 | } 85 | 86 | bool Scheduler::add(Process &process, bool enableIfNot) 87 | { 88 | QueableOperation op(&process, QueableOperation::ADD_SERVICE); 89 | bool ret = op.queue(_queue); 90 | if (ret && enableIfNot) 91 | ret &= enable(process); 92 | return ret; 93 | } 94 | 95 | bool Scheduler::destroy(Process &process) 96 | { 97 | QueableOperation op(&process, QueableOperation::DESTROY_SERVICE); 98 | return op.queue(_queue); 99 | } 100 | 101 | 102 | bool Scheduler::restart(Process &process) 103 | { 104 | QueableOperation op(&process, QueableOperation::RESTART_SERVICE); 105 | return op.queue(_queue); 106 | } 107 | 108 | bool Scheduler::halt() 109 | { 110 | QueableOperation op(QueableOperation::HALT); 111 | return op.queue(_queue); 112 | } 113 | 114 | uint8_t Scheduler::getID(Process &process) 115 | { 116 | return process.getID(); 117 | } 118 | 119 | uint8_t Scheduler::countProcesses(int priority, bool enabledOnly) 120 | { 121 | 122 | uint8_t count=0; 123 | for (uint8_t i = (priority == ALL_PRIORITY_LEVELS) ? 0 : (uint8_t)priority; i < NUM_PRIORITY_LEVELS; i++) 124 | { 125 | for (Process *curr = _pLevels[i].head; curr != NULL; curr = curr->getNext()) 126 | { 127 | count += enabledOnly ? curr->isEnabled() : 1; 128 | } 129 | if (priority != ALL_PRIORITY_LEVELS) 130 | break; 131 | } 132 | 133 | return count; 134 | } 135 | 136 | 137 | int Scheduler::run() 138 | { 139 | // Already running in another call frame 140 | if (_active) return 0; 141 | 142 | uint8_t count = 0; 143 | uint32_t start = getCurrTS(); 144 | for (uint8_t pLevel=0; pLevel < NUM_PRIORITY_LEVELS; pLevel++) 145 | { 146 | processQueue(); 147 | 148 | // Resume looking where we left off in the list 149 | Process *torun = getRunnable(start, _pLevels[pLevel].next, NULL); 150 | 151 | if (!torun && _pLevels[pLevel].next != _pLevels[pLevel].head) 152 | torun = getRunnable(start, _pLevels[pLevel].head, _pLevels[pLevel].next); 153 | 154 | // No ready process found at this priority level 155 | if (!torun) 156 | continue; 157 | 158 | _pLevels[pLevel].next = torun->hasNext() ? torun->getNext() : _pLevels[pLevel].head; 159 | 160 | /////////// Run the correct process ///////// 161 | _active = torun; 162 | start = getCurrTS(); //update 163 | bool force = _active->forceSet(); // Store whether it was a forced iteraiton 164 | _active->willService(start); 165 | 166 | #ifdef _PROCESS_EXCEPTION_HANDLING 167 | int ret = setjmp(_env); 168 | 169 | // Enable the interrupts 170 | #ifdef _PROCESS_TIMEOUT_INTERRUPTS 171 | ENABLE_SCHEDULER_ISR(); 172 | #endif 173 | 174 | if (!ret) { 175 | _active->service(); 176 | } else { 177 | jmpHandler(ret); 178 | } 179 | #else 180 | _active->service(); 181 | #endif 182 | 183 | // Disable the interrupts after the process returned 184 | #ifdef _PROCESS_TIMEOUT_INTERRUPTS 185 | DISABLE_SCHEDULER_ISR(); 186 | #endif 187 | //////////////////////END PROCESS SERVICING////////////////////// 188 | 189 | #ifdef _PROCESS_STATISTICS 190 | uint32_t runTime = getCurrTS() - start; 191 | // Make sure no overflow happens 192 | if (_active->statsWillOverflow(1, runTime)) 193 | handleHistOverFlow(HISTORY_DIV_FACTOR); 194 | 195 | _active->setHistIterations(_active->getHistIterations()+1); 196 | _active->setHistRuntime(_active->getHistRunTime()+runTime); 197 | 198 | #endif 199 | // Is it time to disable? 200 | if (_active->wasServiced(force)) { 201 | disable(*_active); 202 | } 203 | _active = NULL; //done! 204 | 205 | count++; // incr counter 206 | processQueue(); 207 | delay(0); // For esp8266 208 | break; // We found the process and serviced it, so were done 209 | } 210 | delay(0); // For esp8266 211 | 212 | return count; 213 | } 214 | 215 | 216 | // end is exclusive, end=NULL means go to entire end of list 217 | Process *Scheduler::getRunnable(uint32_t start, Process *begin, Process *end) 218 | { 219 | if (!start) 220 | return NULL; 221 | 222 | Process *torun = NULL; 223 | Process *tmp = begin; 224 | 225 | // Search for the best process 226 | while(tmp != end) { 227 | if (tmp->needsServicing(start)) { 228 | if (torun) { //Compare which one needs to run more 229 | torun = Process::runWhich(torun, tmp); 230 | } else { //torun is NULL so this is the best one to run 231 | torun = tmp; 232 | } 233 | } 234 | 235 | tmp = tmp->getNext(); 236 | } 237 | 238 | return torun; 239 | } 240 | 241 | 242 | /************ PROTECTED ***************/ 243 | void Scheduler::procDisable(Process &process) 244 | { 245 | if (process.isEnabled() && isNotDestroyed(process)) { 246 | process.onDisable(); 247 | process.setDisabled(); 248 | } 249 | } 250 | 251 | 252 | void Scheduler::procEnable(Process &process) 253 | { 254 | if (!process.isEnabled() && isNotDestroyed(process)) { 255 | process.resetTimeStamps(); 256 | process.onEnable(); 257 | process.setEnabled(); 258 | } 259 | } 260 | 261 | 262 | void Scheduler::procDestroy(Process &process) 263 | { 264 | if (isNotDestroyed(process)) { 265 | procDisable(process); 266 | process.cleanup(); 267 | removeNode(process); 268 | process.setID(0); 269 | } 270 | } 271 | 272 | 273 | void Scheduler::procRestart(Process &process) 274 | { 275 | if (isNotDestroyed(process)) { 276 | procDisable(process); 277 | process.cleanup(); 278 | } 279 | process.resetTimeStamps(); 280 | process.setup(); 281 | procEnable(process); 282 | } 283 | 284 | 285 | void Scheduler::procAdd(Process &process) 286 | { 287 | if (!isNotDestroyed(process)) { 288 | for (; process.getID() == 0 || findProcById(process.getID()) != NULL; process.setID(++_lastID)); // Find a free id 289 | process.resetTimeStamps(); 290 | process.setup(); 291 | procEnable(process); 292 | appendNode(process); 293 | 294 | #ifdef _PROCESS_STATISTICS 295 | process.setHistRuntime(0); 296 | process.setHistIterations(0); 297 | process.setHistLoadPercent(0); 298 | #endif 299 | } 300 | } 301 | 302 | void Scheduler::procHalt() 303 | { 304 | 305 | for (uint8_t i = 0; i < NUM_PRIORITY_LEVELS; i++) 306 | { 307 | for (Process *curr = _pLevels[i].head; curr != NULL; curr = curr->getNext()) 308 | { 309 | procDestroy(*curr); 310 | } 311 | } 312 | 313 | delay(100); 314 | 315 | HALT_PROCESSOR(); 316 | } 317 | 318 | 319 | /* Queue object */ 320 | // This is so ugly, stupid namespace crap 321 | Scheduler::QueableOperation::QueableOperation() : _process(NULL), _operation(static_cast(NONE)) {} 322 | 323 | Scheduler::QueableOperation::QueableOperation(Scheduler::QueableOperation::QueableOperation::OperationType op) 324 | : _process(NULL), _operation(static_cast(op)) {} 325 | 326 | Scheduler::QueableOperation::QueableOperation(Process *serv, Scheduler::QueableOperation::QueableOperation::OperationType op) 327 | : _process(serv), _operation(static_cast(op)) {} 328 | 329 | Process *Scheduler::QueableOperation::getProcess() 330 | { 331 | return _process; 332 | } 333 | 334 | Scheduler::QueableOperation::QueableOperation::OperationType Scheduler::QueableOperation::getOperation() 335 | { 336 | return static_cast(_operation); 337 | } 338 | 339 | bool Scheduler::QueableOperation::queue(RingBuf *queue) 340 | { 341 | return queue->add(queue, this) >= 0; 342 | } 343 | 344 | /* end Queue object garbage */ 345 | 346 | 347 | //Only call when there is guarantee this is not running in another call frame 348 | void Scheduler::processQueue() 349 | { 350 | while(!_queue->isEmpty(_queue)) // Empty Queue 351 | { 352 | QueableOperation op; 353 | _queue->pull(_queue, &op); 354 | switch (op.getOperation()) 355 | { 356 | case QueableOperation::ENABLE_SERVICE: 357 | procEnable(*op.getProcess()); 358 | break; 359 | 360 | case QueableOperation::DISABLE_SERVICE: 361 | procDisable(*op.getProcess()); 362 | break; 363 | 364 | case QueableOperation::ADD_SERVICE: 365 | procAdd(*op.getProcess()); 366 | break; 367 | 368 | case QueableOperation::DESTROY_SERVICE: 369 | procDestroy(*op.getProcess()); 370 | break; 371 | 372 | case QueableOperation::RESTART_SERVICE: 373 | procRestart(*op.getProcess()); 374 | break; 375 | 376 | case QueableOperation::HALT: 377 | procHalt(); 378 | break; 379 | 380 | #ifdef _PROCESS_STATISTICS 381 | case QueableOperation::UPDATE_STATS: 382 | procUpdateStats(); 383 | break; 384 | #endif 385 | 386 | default: 387 | break; 388 | } 389 | } 390 | } 391 | 392 | #ifdef _PROCESS_STATISTICS 393 | bool Scheduler::updateStats() 394 | { 395 | QueableOperation op(QueableOperation::UPDATE_STATS); 396 | return op.queue(_queue); 397 | } 398 | 399 | 400 | void Scheduler::procUpdateStats() 401 | { 402 | 403 | uint8_t count = countProcesses(ALL_PRIORITY_LEVELS, false); 404 | hTimeCount_t sTime[count]; 405 | 406 | // Thread safe in case of interrupts 407 | hTimeCount_t totalTime = 0; 408 | Process *p; 409 | uint8_t i; 410 | 411 | for (uint8_t l = 0; l < NUM_PRIORITY_LEVELS; l++) 412 | { 413 | for (i = 0, p = _pLevels[l].head; p != NULL && i < count; p = p->getNext(), i++) 414 | { 415 | // to ensure no overflows 416 | sTime[i] = (p->getHistRunTime() + count/2) / count; 417 | totalTime += sTime[i]; 418 | } 419 | } 420 | 421 | for (uint8_t l = 0; l < NUM_PRIORITY_LEVELS; l++) 422 | { 423 | for (i = 0, p = _pLevels[l].head; p != NULL && i < count; p = p->getNext(), i++) 424 | { 425 | // to ensure no overflows have to use double 426 | if (!totalTime) { 427 | p->setHistLoadPercent(0); 428 | } else { 429 | double tmp = 100*((double)sTime[i]/(double)totalTime); 430 | p->setHistLoadPercent((uint8_t)tmp); 431 | } 432 | } 433 | } 434 | 435 | return; 436 | } 437 | 438 | // Make sure it is locked 439 | void Scheduler::handleHistOverFlow(uint8_t div) 440 | { 441 | for (uint8_t i = 0; i < NUM_PRIORITY_LEVELS; i++) 442 | { 443 | for (Process *p = _pLevels[i].head; p != NULL; p = p->getNext()) 444 | { 445 | p->divStats(div); 446 | } 447 | } 448 | 449 | } 450 | 451 | #endif 452 | 453 | 454 | #ifdef _PROCESS_EXCEPTION_HANDLING 455 | void Scheduler::raiseException(int e) 456 | { 457 | longjmp(_env, e); 458 | } 459 | 460 | void Scheduler::handleException(Process *process, int e) 461 | { 462 | // Exception came from process 463 | if (process) { 464 | process->restart(); 465 | } else { 466 | // Exception came from scheduler 467 | 468 | } 469 | } 470 | 471 | 472 | bool Scheduler::jmpHandler(int e) 473 | { 474 | if (e != 0 && _active) 475 | { 476 | switch(e) 477 | { 478 | #ifdef _PROCESS_TIMEOUT_INTERRUPTS 479 | case LONGJMP_ISR_CODE: 480 | _active->handleWarning(ERROR_PROC_TIMED_OUT); 481 | break; 482 | #endif 483 | case LONGJMP_YIELD_CODE: 484 | //no nothing 485 | break; 486 | 487 | default: 488 | if (!_active->handleException(e)) 489 | handleException(_active, e); 490 | break; 491 | 492 | } 493 | return true; 494 | } 495 | return false; 496 | } 497 | #endif 498 | 499 | 500 | 501 | bool Scheduler::appendNode(Process &node) 502 | { 503 | node.setNext(NULL); 504 | 505 | ProcPriority p = node.getPriority(); 506 | 507 | if (!_pLevels[p].head) { // adding to head 508 | _pLevels[p].head = &node; 509 | _pLevels[p].next = &node; 510 | } else { 511 | Process *next = _pLevels[p].head; // adding not to head 512 | for(; next->hasNext(); next = next->getNext()); //run through list 513 | // Update pointers 514 | next->setNext(&node); 515 | } 516 | return true; 517 | } 518 | 519 | bool Scheduler::removeNode(Process &node) 520 | { 521 | ProcPriority p = node.getPriority(); 522 | 523 | if (&node == _pLevels[p].head) { // node is head 524 | _pLevels[p].head = node.getNext(); 525 | } else { 526 | // Find the previous node 527 | Process *prev = findPrevNode(node); 528 | 529 | if (!prev) 530 | return false; // previous node does not exist 531 | 532 | prev->setNext(node.getNext()); 533 | } 534 | 535 | if (_pLevels[p].next == &node) 536 | _pLevels[p].next = node.hasNext() ? node.getNext() : _pLevels[p].head; 537 | 538 | return true; 539 | } 540 | 541 | bool Scheduler::findNode(Process &node) 542 | { 543 | for (Process *p = _pLevels[node.getPriority()].head; p != NULL; p = p->getNext()) 544 | { 545 | if (p == &node) 546 | return true; 547 | } 548 | return false; 549 | } 550 | 551 | Process *Scheduler::findPrevNode(Process &node) 552 | { 553 | Process *prev = _pLevels[node.getPriority()].head; 554 | for (; prev != NULL && prev->getNext() != &node; prev = prev->getNext()); 555 | return prev; 556 | } 557 | 558 | /* 559 | void Scheduler::reOrderProcs(ProcPriority level) 560 | { 561 | for(Process *p = _pLevels[level].head; p != NULL && p->hasNext(); p = p->getNext()) 562 | { 563 | Process *next = p->getNext(); 564 | 565 | if (p->getAvgRunTime() > next->getAvgRunTime()) 566 | { 567 | Serial.println("Swapping"); 568 | if(!swapNode(*p, *next)) //this should never fail, but to be safe 569 | break; 570 | 571 | p = next; 572 | } 573 | } 574 | } 575 | 576 | bool Scheduler::swapNode(Process &n1, Process &n2) 577 | { 578 | if (&n1 == &n2 || n1.getPriority() != n2.getPriority()) 579 | return false; 580 | 581 | 582 | Process *n1_prev = findPrevNode(n1); 583 | Process *n2_prev = findPrevNode(n2); 584 | 585 | if (!n1_prev) { //n1 was head 586 | _pLevels[n1.getPriority()].head = &n2; 587 | } else { //n1 was not head 588 | n1_prev->setNext(&n2); 589 | } 590 | 591 | if (!n2_prev) { //n2 was head 592 | _pLevels[n1.getPriority()].head = &n1; 593 | } else { //n2 was not head 594 | n2_prev->setNext(&n1); 595 | } 596 | 597 | Process *tmp = n2.getNext(); 598 | n2.setNext(n1.getNext()); 599 | n1.setNext(tmp); 600 | return true; 601 | } 602 | 603 | bool Scheduler::removeNodeAfter(Process &prev) 604 | { 605 | Process *toRemove = prev.getNext(); 606 | 607 | if (!toRemove) 608 | return false; 609 | 610 | prev.setNext(toRemove->getNext()); 611 | 612 | return true; 613 | } 614 | 615 | bool Scheduler::insertNodeAfter(Process &prev, Process &toAdd) 616 | { 617 | Process *next = prev.getNext(); 618 | 619 | prev.setNext(&toAdd); 620 | toAdd.setNext(&prev); 621 | 622 | return true; 623 | } 624 | */ 625 | -------------------------------------------------------------------------------- /src/ProcessScheduler/Scheduler.h: -------------------------------------------------------------------------------- 1 | #ifndef SCHEDULER_H 2 | #define SCHEDULER_H 3 | 4 | #include "Includes.h" 5 | 6 | typedef struct RingBuf RingBuf; 7 | 8 | class Process; 9 | 10 | 11 | 12 | class Scheduler 13 | { 14 | 15 | public: 16 | Scheduler(); 17 | ~Scheduler(); 18 | 19 | /*************** Methods to Perform Actions on Processes ****************/ 20 | // NOTE: These can also be called directly on the Process object 21 | // example process.add() 22 | 23 | /** 24 | * Add a new process to the scheduler chain 25 | * If it is already added, do nothing 26 | * This will trigger the Processes setup() method 27 | * 28 | * @return: True on success 29 | */ 30 | bool add(Process &process, bool enableIfNot = false); 31 | 32 | 33 | /** 34 | * Disable a process 35 | * If it is not part of chain or already disabled, do nothing 36 | * This will trigger the Processes onDisable() method 37 | * 38 | * @return: True on success 39 | */ 40 | bool disable(Process &process); 41 | 42 | 43 | /** 44 | * Enable a process 45 | * If it is not part of chain or already enabled, do nothing 46 | * This will trigger the Processes onEnable() method 47 | * 48 | * @return: True on success 49 | */ 50 | bool enable(Process &process); 51 | 52 | 53 | /** 54 | * Remove a process from the scheduling chain 55 | * If it is not part of chain, do nothing 56 | * This will trigger the Processes cleanup() method 57 | * NOTE: If it is currently enabled, disable() will automatically be called first 58 | * 59 | * @return: True on success 60 | */ 61 | bool destroy(Process &process); 62 | 63 | /** 64 | * Same as calling destroy(), add(), and enable() at once with less overhead 65 | * NOTE: You are restarting the Process, not resetting the settings that govern the operation 66 | * of the process (iterations, priority, period, etc...). 67 | * 68 | * The the initial set iterations, priority, period, etc... (set via constructor arguments) 69 | * will not be reset to how they were when the process was created. If these have changed 70 | * and you want to change them back, you will have to call the appropriate setters 71 | * (ex: proc.setIterations(100);) 72 | * 73 | * @return: True on success 74 | */ 75 | bool restart(Process &process); 76 | 77 | 78 | /** 79 | * Get the id of process 80 | * 81 | * @return: the unique id, otherwise 0 if not added to a scheduler 82 | */ 83 | uint8_t getID(Process &process); 84 | 85 | 86 | /** 87 | * Determine if the process is currently enabled 88 | * 89 | * @return: bool 90 | */ 91 | bool isEnabled(Process &process); 92 | 93 | 94 | /** 95 | * Determine if the scheduler is currently in the middle of servicing this process 96 | * 97 | * @return: bool 98 | */ 99 | bool isRunningProcess(Process &process); 100 | 101 | 102 | /** 103 | * Determine if the process is currently destroyed (not part of the scheduler chain) 104 | * 105 | * @return: bool 106 | */ 107 | bool isNotDestroyed(Process &process); 108 | 109 | /*********************** End Processes Methods ********************/ 110 | 111 | 112 | /** 113 | * Destroy all processes then put the processor into a low power sleep state 114 | * 115 | * @return: True on success 116 | */ 117 | bool halt(); 118 | 119 | 120 | /** 121 | * Get the currently running process 122 | * 123 | * @return: a pointer to the process, NULL on no process currently being run 124 | */ 125 | static Process *getActive(); 126 | 127 | /** 128 | * Get a pointer to the process with id 129 | * 130 | * @return: a pointer to the process, NULL on no process with that id 131 | */ 132 | Process *findProcById(uint8_t id); 133 | 134 | /** 135 | * Get the number of processes in the scheduler chain at priority levels 136 | * If priority = ALL_PRIORITY_LEVELS, count at all priority levels 137 | * If enabledOnly = true, only the enabled processes will be counted 138 | * 139 | * @return: a pointer to the process, NULL on no process with that id 140 | */ 141 | uint8_t countProcesses(int priority = ALL_PRIORITY_LEVELS, bool enabledOnly = true); 142 | 143 | /** 144 | * Get the internal timestamp the scheduler is using to track time 145 | * Either the same as millis() or micros() depending on _MICROS_PRECISION 146 | * @return: uint32_t 147 | */ 148 | static uint32_t getCurrTS(); 149 | 150 | /** 151 | * Run one pass through the scheduler, call this repeatedly in your void loop() 152 | * 153 | * @return: The number of processes serviced in that pass 154 | */ 155 | int run(); 156 | 157 | 158 | // Enable this option in config.h to track time statistics on processes 159 | #ifdef _PROCESS_STATISTICS 160 | /** 161 | * This will update the Process.getLoadPercent() method 162 | * It will estimate the % CPU time for all processes 163 | * NOTE: This will simply add this task to the scheduler job queue 164 | * The update will not happen until the scheduler gets a chance to process the request 165 | * 166 | * @return: True on success 167 | */ 168 | bool updateStats(); 169 | 170 | #endif 171 | 172 | // Enable this option to allow processes to raise and catch custom exceptions 173 | // Behind the scenes this is using setjmp and longjmp 174 | #ifdef _PROCESS_EXCEPTION_HANDLING 175 | /** 176 | * Raise Exception with code e inside a Process service routine 177 | * Execution will stop immediatley, and the processes handleException() will be called 178 | * NOTE: DO NOT CALL THIS FROM OUTSIDE A PROCESS SERVICE ROUTINE 179 | * @return: True on success 180 | */ 181 | void raiseException(int e); 182 | 183 | static jmp_buf _env; // have to do this to access it from ISR 184 | #endif 185 | 186 | ////////// YOU CAN IGNORE ALL THE PROTECTED/PRIVATE METHODS BELOW HERE ///////// 187 | 188 | protected: 189 | #ifdef _PROCESS_EXCEPTION_HANDLING 190 | /* 191 | * Handle uncaught Process exceptions from Process process with Exception code e 192 | * By default just restart it 193 | */ 194 | virtual void handleException(Process *process, int e); 195 | #endif 196 | // Inner queue object class to queue scheduler jobs 197 | class QueableOperation 198 | { 199 | public: 200 | enum OperationType 201 | { 202 | NONE = 0, 203 | ADD_SERVICE, 204 | DESTROY_SERVICE, 205 | DISABLE_SERVICE, 206 | ENABLE_SERVICE, 207 | RESTART_SERVICE, 208 | HALT, 209 | #ifdef _PROCESS_STATISTICS 210 | UPDATE_STATS, 211 | #endif 212 | }; 213 | 214 | QueableOperation(); 215 | QueableOperation(OperationType op); 216 | QueableOperation(Process *serv, OperationType op); 217 | 218 | Process *getProcess(); 219 | OperationType getOperation(); 220 | bool queue(RingBuf *queue); 221 | 222 | private: 223 | Process *_process; 224 | const uint8_t _operation; 225 | }; 226 | 227 | 228 | #ifdef _PROCESS_EXCEPTION_HANDLING 229 | // handle the return from setjmp() 230 | bool jmpHandler(int e); 231 | #endif 232 | 233 | #ifdef _PROCESS_STATISTICS 234 | // Actually do the update that was requested in updateStats() 235 | void procUpdateStats(); 236 | // This will handle overflow condiitions on the statistics 237 | void handleHistOverFlow(uint8_t div); 238 | #endif 239 | 240 | // Methods that process queued operations 241 | void procDisable(Process &process); 242 | void procEnable(Process &process); 243 | void procDestroy(Process &process); 244 | void procAdd(Process &process); 245 | void procRestart(Process &process); 246 | void procHalt(); 247 | 248 | // Get runnable process in process linked list chain 249 | Process *getRunnable(uint32_t start, Process *begin, Process *end=NULL); 250 | 251 | // Process the scheduler job queue 252 | void processQueue(); 253 | 254 | // Linked list methods 255 | bool appendNode(Process &node); // true on success 256 | bool removeNode(Process &node); // true on success 257 | bool findNode(Process &node); // True if node exists in list 258 | Process *findPrevNode(Process &node); 259 | 260 | 261 | static Process *_active; // needs to be static for access in ISR 262 | uint8_t _lastID; 263 | RingBuf *_queue; 264 | 265 | struct SchedulerPriorityLevel 266 | { 267 | Process *head; 268 | Process *next; 269 | }; 270 | struct SchedulerPriorityLevel _pLevels[NUM_PRIORITY_LEVELS]; 271 | 272 | 273 | /* CUSTOM COMPILE OPTIONS*/ 274 | /* 275 | bool swapNode(Process &n1, Process &n2); 276 | void reOrderProcs(ProcPriority level); 277 | */ 278 | 279 | }; 280 | 281 | #endif 282 | --------------------------------------------------------------------------------