Skip to content

Commit cb19de0

Browse files
authored
Merge pull request #32 from davetcc/enable-task-work
Changes for task enable/disable task and also for schedules over an hour.
2 parents 5b99642 + 7f1bc35 commit cb19de0

File tree

10 files changed

+273
-37
lines changed

10 files changed

+273
-37
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* An example that shows how to create long schedules, in hours and days with task manager. Any schedule over an hour
3+
* in length can be created using a TmLongSchedule instance, these instances are registered as events with task manager
4+
* and other than this work as usual.
5+
*
6+
* You cannot create schedules over 60 minutes using the scheduleFixedRate / scheduleOnce functions, instead you use
7+
* the procedure below.
8+
*/
9+
10+
#include <TaskManagerIO.h>
11+
#include <TmLongSchedule.h>
12+
#include <Wire.h>
13+
14+
//
15+
// Here we create an OO task (a task that is actually a class instance). In this case the exec() method will be
16+
// called at the specified time.
17+
//
18+
class MyTaskExecutable : public Executable {
19+
private:
20+
int callCount;
21+
bool enableTask = false;
22+
taskid_t taskToSuspend = TASKMGR_INVALIDID;
23+
public:
24+
void setTaskToSuspend(taskid_t id) { taskToSuspend = id; }
25+
26+
void exec() override {
27+
callCount++;
28+
Serial.print("Called my task executable ");
29+
Serial.println(callCount);
30+
31+
taskManager.setTaskEnabled(taskToSuspend, enableTask);
32+
enableTask = !enableTask;
33+
}
34+
} myTaskExec;
35+
36+
// Forward references
37+
void dailyScheduleFn();
38+
39+
//
40+
// Here we create two schedules to be registered with task manager later. One will fire every days and one will fire
41+
// every hour and a half. You provide the schedule in milliseconds (generally using the two helper functions shown) and
42+
// also either a timer function or class extending executable.
43+
//
44+
TmLongSchedule hourAndHalfSchedule(makeHourSchedule(1, 30), &myTaskExec);
45+
TmLongSchedule onceADaySchedule(makeDaySchedule(1), dailyScheduleFn);
46+
47+
void setup() {
48+
Serial.begin(115200);
49+
50+
Serial.println("Started long schedule example");
51+
52+
// First two long schedules are global variables.
53+
// IMPORTANT NOTE: If you use references to a variable like this THEY MUST BE GLOBAL
54+
taskManager.registerEvent(&hourAndHalfSchedule);
55+
taskManager.registerEvent(&onceADaySchedule);
56+
57+
// this shows how to create a long schedule event using the new operator, make sure the second parameter is true
58+
// as this will delete the event when it completes.
59+
taskManager.registerEvent(new TmLongSchedule(makeHourSchedule(0, 15), [] {
60+
Serial.print(millis());
61+
Serial.println(": Fifteen minutes passed");
62+
}), true);
63+
64+
// lastly we show the regular event creation method, this task is enabled and disabled by the OO task.
65+
auto taskId = taskManager.scheduleFixedRate(120, [] {
66+
Serial.print(millis());
67+
Serial.println(": Two minutes");
68+
}, TIME_SECONDS);
69+
70+
myTaskExec.setTaskToSuspend(taskId);
71+
}
72+
73+
void loop() {
74+
taskManager.runLoop();
75+
}
76+
77+
void dailyScheduleFn() {
78+
Serial.print(millis());
79+
Serial.println(": Daily schedule");
80+
}

library.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"maintainer": true
1414
}
1515
],
16-
"version": "1.2.3",
16+
"version": "1.3.0",
1717
"license": "Apache-2.0",
1818
"frameworks": "arduino, mbed",
1919
"platforms": "*"

library.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name=TaskManagerIO
2-
version=1.2.3
2+
version=1.3.0
33
maintainer=https://www.thecoderscorner.com
44
author=davetcc
55
category=Other

src/TaskManagerIO.cpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,15 @@ taskid_t TaskManager::registerEvent(BaseEvent *eventToAdd, bool deleteWhenDone)
154154
return taskId;
155155
}
156156

157+
void TaskManager::setTaskEnabled(taskid_t taskId, bool ena) {
158+
auto task = getTask(taskId);
159+
if(task == nullptr || !task->isInUse()) return;
160+
161+
task->setEnabled(ena);
162+
163+
if(ena) putItemIntoQueue(task);
164+
}
165+
157166
void TaskManager::cancelTask(taskid_t taskId) {
158167
auto task = getTask(taskId);
159168
// always create a new task to ensure the task is never, ever cancelled on anything other than the task thread.
@@ -240,6 +249,9 @@ void TaskManager::putItemIntoQueue(TimerTask* tm) {
240249
// we must own the lock before adding to the queue, as someone else could be removing.
241250
TmSpinLock spinLock(&memLockerFlag);
242251

252+
// we can never schedule a task that is not enabled.
253+
if(!tm->isEnabled()) return;
254+
243255
auto theFirst = tm_internal::atomicReadPtr(&first);
244256

245257
// shortcut, no first yet, so we are at the top!
@@ -249,7 +261,7 @@ void TaskManager::putItemIntoQueue(TimerTask* tm) {
249261
return;
250262
}
251263

252-
// if we need to execute now or before the next task, then we are first. For performance we use unfair semantics
264+
// if we need to execute now or before the next task, then we are first. For performance, we use unfair semantics
253265
if (theFirst->microsFromNow() >= tm->microsFromNow()) {
254266
tm->setNext(theFirst);
255267
tm_internal::atomicWritePtr(&first, tm);
@@ -271,7 +283,6 @@ void TaskManager::putItemIntoQueue(TimerTask* tm) {
271283
}
272284

273285
// we are at the end of the queue
274-
// always set
275286
tm->setNext(nullptr);
276287
previous->setNext(tm);
277288
}
@@ -286,6 +297,9 @@ void TaskManager::removeFromQueue(TimerTask* tm) {
286297
TmSpinLock spinLock(&memLockerFlag);
287298
auto theFirst = tm_internal::atomicReadPtr(&first);
288299

300+
// there must be at least one item to proceed.
301+
if(theFirst == nullptr) return;
302+
289303
// shortcut, if we are first, just remove us by getting the next and setting first.
290304
if (theFirst == tm) {
291305
tm_internal::atomicWritePtr(&first, tm->getNext());

src/TaskManagerIO.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,15 @@ class TaskManager {
215215
*/
216216
void cancelTask(taskid_t task);
217217

218+
/**
219+
* Sets a tasks enable status. An enabled task is scheduled whereas a disabled task is not scheduled. Note that
220+
* after disabling a task it may run one more time before switching state. Upon re-enablement then task manager
221+
* will put the item back into the run queue if needed.
222+
* @param task the task to change status
223+
* @param ena the enablement status
224+
*/
225+
void setTaskEnabled(taskid_t task, bool ena);
226+
218227
/**
219228
* Use instead of delays or wait loops inside code that needs to perform timing functions. It will
220229
* not call back until at least `micros` time has passed.

src/TaskTypes.cpp

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -67,19 +67,6 @@ void TimerTask::initialiseEvent(BaseEvent* event, bool deleteWhenDone) {
6767
this->executeMode = deleteWhenDone ? ExecutionType(EXECTYPE_EVENT | EXECTYPE_DELETE_ON_DONE) : EXECTYPE_EVENT;
6868
}
6969

70-
bool TimerTask::isReady() {
71-
if (!isInUse() || isRunning()) return false;
72-
73-
if ((isMicrosSchedule()) != 0) {
74-
uint32_t delay = myTimingSchedule;
75-
return (micros() - scheduledAt) >= delay;
76-
}
77-
else {
78-
uint32_t delay = myTimingSchedule;
79-
return (millis() - scheduledAt) >= delay;
80-
}
81-
}
82-
8370
unsigned long TimerTask::microsFromNow() {
8471
uint32_t microsFromNow;
8572
if (isMicrosSchedule()) {
@@ -98,6 +85,8 @@ unsigned long TimerTask::microsFromNow() {
9885
void TimerTask::execute() {
9986
RunningState runningState(this);
10087

88+
if(!isEnabled()) return;
89+
10190
auto execType = (ExecutionType) (executeMode & EXECTYPE_MASK);
10291
switch (execType) {
10392
case EXECTYPE_EVENT:
@@ -112,7 +101,7 @@ void TimerTask::execute() {
112101
break;
113102
}
114103

115-
if (isRepeating()) {
104+
if (isRepeating() && isEnabled()) {
116105
this->scheduledAt = isMicrosSchedule() ? micros() : millis();
117106
}
118107
}
@@ -146,6 +135,24 @@ void TimerTask::processEvent() {
146135
scheduledAt = micros();
147136
}
148137

138+
void TimerTask::setEnabled(bool ena) {
139+
// We really don't want to do bit-masking directly on a volatile field. Copy to local first.
140+
uint8_t execTy = executeMode;
141+
bitWrite(execTy, EXECMODE_BIT_DISABLED, (!ena));
142+
executeMode = static_cast<ExecutionType>(execTy);
143+
}
144+
145+
bool TimerTask::isRepeating() const {
146+
if(ExecutionType(executeMode & EXECTYPE_MASK) == EXECTYPE_EVENT) {
147+
// if it's an event it repeats until the event is considered "complete"
148+
return !eventRef->isComplete();
149+
}
150+
else {
151+
// otherwise it's based on the task repeating flag
152+
return 0 != (timingInformation & TM_TIME_REPEATING);
153+
}
154+
}
155+
149156
void BaseEvent::markTriggeredAndNotify() {
150157
setTriggered(true);
151158
taskMgrAssociation->triggerEvents();

src/TaskTypes.h

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,10 @@ enum TimerUnit : uint8_t {
136136

137137
TM_TIME_REPEATING = 0x10,
138138
TM_TIME_RUNNING = 0x20,
139-
140-
TIME_REP_MICROS = TIME_MICROS | TM_TIME_REPEATING,
141-
TIME_REP_MILLIS = TIME_MILLIS | TM_TIME_REPEATING,
142139
};
143140

141+
#define EXECMODE_BIT_DISABLED 4
142+
144143
/**
145144
* Internal class.
146145
* The execution types stored internally in a task, records what kind of task is in use, and if it needs deleting
@@ -153,6 +152,7 @@ enum ExecutionType : uint8_t {
153152

154153
EXECTYPE_MASK = 0x03,
155154
EXECTYPE_DELETE_ON_DONE = 0x08,
155+
EXECTYPE_TASK_DISABLED = 0x10,
156156

157157
EXECTYPE_DEL_EXECUTABLE = EXECTYPE_EXECUTABLE | EXECTYPE_DELETE_ON_DONE,
158158
EXECTYPE_DEL_EVENT = EXECTYPE_EVENT | EXECTYPE_DELETE_ON_DONE
@@ -201,12 +201,6 @@ class TimerTask {
201201
public:
202202
TimerTask();
203203

204-
/**
205-
* Checks if 1. the task is in use, and 2. if the task is ready for execution.
206-
* @return true if the task is ready to execute.
207-
*/
208-
bool isReady();
209-
210204
/**
211205
* @return the number of microseconds before execution is to take place, 0 means it's due or past due.
212206
*/
@@ -253,16 +247,7 @@ class TimerTask {
253247
* Checks if this task is a repeating task.
254248
* @return true if repeating, otherwise false.
255249
*/
256-
bool isRepeating() const {
257-
if(ExecutionType(executeMode & EXECTYPE_MASK) == EXECTYPE_EVENT) {
258-
// if it's an event it repeats until the event is considered "complete"
259-
return !eventRef->isComplete();
260-
}
261-
else {
262-
// otherwise it's based on the task repeating flag
263-
return 0 != (timingInformation & TM_TIME_REPEATING);
264-
}
265-
}
250+
bool isRepeating() const;
266251

267252
/**
268253
* Take a task out of use and clear down all it's fields. Clears the in use flag last for thread safety
@@ -334,6 +319,17 @@ class TimerTask {
334319
* @return true if the task in on a millisecond schedule
335320
*/
336321
bool isMillisSchedule() { return (timingInformation & 0x0fU)==TIME_MILLIS; }
322+
323+
/**
324+
* @return if the task is presently enabled - IE it is being scheduled.
325+
*/
326+
bool isEnabled() { return !bitRead(executeMode, EXECMODE_BIT_DISABLED) != 0; }
327+
328+
/**
329+
* Set the task aspi either enabled or disabled. When enabled it is scheduled, otherwise it is not scheduled.
330+
* @param ena the enablement status
331+
*/
332+
void setEnabled(bool ena);
337333
};
338334

339335
#endif //TASKMANAGER_IO_TASKTYPES_H

src/TmLongSchedule.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright (c) 2018-present https://www.thecoderscorner.com (Nutricherry LTD).
3+
* This product is licensed under an Apache license, see the LICENSE file in the top-level directory.
4+
*/
5+
6+
#include "TmLongSchedule.h"
7+
8+
#define HOURS_TO_MILLIS 3600000UL
9+
#define MINUTES_TO_MILLIS 60000UL
10+
#define SIX_MINUTES_TO_MILLIS 360000UL
11+
12+
uint32_t makeHourSchedule(int hours, int minutes, int seconds, int millis) {
13+
return (hours * HOURS_TO_MILLIS) + (minutes * MINUTES_TO_MILLIS) + (seconds * 1000) + millis;
14+
}
15+
16+
uint32_t makeDaySchedule(int days, int hours) {
17+
return (days * 24UL * HOURS_TO_MILLIS) + (hours * MINUTES_TO_MILLIS);
18+
}
19+
20+
TmLongSchedule::TmLongSchedule(uint32_t milliScheduleNext, Executable* toExecute) : milliSchedule(milliScheduleNext),
21+
theExecutable(toExecute), lastScheduleTime(0), isTimerFn(false) { }
22+
23+
TmLongSchedule::TmLongSchedule(uint32_t milliScheduleNext, TimerFn toExecute) : milliSchedule(milliScheduleNext),
24+
fnCallback(toExecute), lastScheduleTime(0), isTimerFn(true) { }
25+
26+
void TmLongSchedule::exec() {
27+
lastScheduleTime = millis();
28+
29+
// never set last schedule time as 0. It is an invalid state.
30+
if(lastScheduleTime == 0) lastScheduleTime = 1;
31+
32+
if(isTimerFn && theExecutable != nullptr) {
33+
fnCallback();
34+
}
35+
else {
36+
theExecutable->exec();
37+
}
38+
}
39+
40+
uint32_t TmLongSchedule::timeOfNextCheck() {
41+
// initial state when schedule time is zero, we need avoid running the task at start up and most certainly should
42+
// not call millis in a global constructor, who knows what's initialised at that point.
43+
if(lastScheduleTime == 0) lastScheduleTime = millis();
44+
45+
// Do not modify this code without fully understanding clock roll and unsigned values.
46+
uint32_t alreadyTaken = (millis() - lastScheduleTime);
47+
auto millisFromNow = (milliSchedule < alreadyTaken) ? 0 : ((milliSchedule - alreadyTaken));
48+
if(millisFromNow == 0) {
49+
// time to trigger, set the event as ready to fire and we'll wait out a full cycle
50+
setTriggered(true);
51+
millisFromNow = milliSchedule;
52+
}
53+
54+
// we'll wait a maximum of six minutes in between testing again.
55+
return (millisFromNow > SIX_MINUTES_TO_MILLIS) ? SIX_MINUTES_TO_MILLIS : millisFromNow;
56+
}

0 commit comments

Comments
 (0)