This article is an update of a previous one related to an event scheduler for Arduino boards; the update describes the changes made to make it usable also for ESP32 boards.
Introduction
This article results in developments of applications using Arduino and ESP32 boards in the Arduino IDE environment. It is an update of the previous article which reflects the changes made to the software.
A characteristic use of this type of card is to control sensors and/or to activate actions by a software which loops indefinitely; below is the skeleton of an Arduino script (called, in the Arduino jargon, sketch):
void setup() {
}
void loop() {
}
The article illustrates a class for schedules activities an Arduino and ESP32 boards: the loop
function contains a call to the scheduler and the developer must only describe the events, instantiate the class (before the setup
function) and write functions to serve the events.
Background
The reader must have some knowledge on how a program running in the automation boards is organized, the language used (the C++ dialects of Arduino) and the concepts of event with the associated action.
Events can occurs in many ways, where the most significant are:
- at every defined time interval
- at prefixed time of day
- on command
Besides, an event can be punctual or with duration, for example, a punctual event can be reading data from a sensor, a duration event can be the start of pump that must operate for certain period of time or a sensor that needs some time to be ready.
Other possibilities, like event that occurs at the beginning or after a period of time, are easily achievable as variants of those listed above.
The Class handleEvents
The software has been improved and tested to be used for both Arduino and ESP32 boards; in particular:
- the event structure has been simplified
- abnormal delays are supported
- the software can be used with techniques of power reduction
Using the Code
The program is based on a set of events
coded by a C++ structure
and a class (named handleEvents
) which contains the scheduler.
Below, there is a template for using the scheduler:
...
cronStructure cron[] = { {...}, {0,0,0,0,NULL,NULL} };
handleEvents events(cron); ...
events.setTime(day/2); ...
void setup() {
...
...
}
void loop() {
delay(events.croner(cron)); }
The class handleEvents
contains the function croner
which analyzes the array of events and, for events that have reached the time of activation, performs their management function.
The class also contains some functions:
clocker(false) | Give the time and day elapsed in seconds, being unsigned long , it can reach 136 years:
numbers of days = clocker(false) / 86400
seconds from midnight = clocker(false) % 86400 |
setTime(time_of_day_seconds) | Set the time adding the value to a 00:00:00 of the same day |
listEvents(cronStructure) | Shows the events (i.e., the actual status) |
Time() | Shows the time in the form HH:MM:SS+DD |
toSeconds("HH:MM:SS") | Transform a time in the form HH:MM:SS in seconds, seconds and minutes may be missing |
Every event
contains the information for his handling (the cronStructure
is contained in the .h file of the class).
typedef void (* fnzPointer) (struct cronStructure *x);
struct cronStructure {
uint32_t every; uint32_t next; uint32_t duration; int8_t status; fnzPointer fnzAction;
const char* name;
};
Every time is expressed in seconds:
every
: This is the interval between two consecutive occurrences of the event, 86400
is a daily event. next
: The time for the next occurrence of the event: when the internal clock become equal or greater of next
, the associated action
is called; the initial value of this field means a delay on start or an effective time of day depending by the value of status
field. duration
: The event duration, if greater than 0
, the function which handles this event is called also when the duration time expires, this permits the control of the start and end of an action. status
: The event status and type when the value of bits below is 1:
- bit 1 event enabled
- bit 2 event active
- bit 3 event active with duration
- bit 4 event manual
- bit 5 event at fixed time
action
: The function which handles the event name
: The event name
The events must be arranged on array, with a NULL
event terminator:
cronStructure cron[] = {{...},{0,0,0,0,NULL,NULL}};
or:
RTC_DATA_ATTR cronStructure cron[] = {{...},{0,0,0,0,NULL,NULL}};
The second form can be used only for ESP32 boards for memorizing the events in the memory of ULP co-processor (that is preserved during the deep sleep), so some data are maintained when the processor enter in power reduction; these data are stored, for all boards, in the NULL
event terminator:
NULL Event field | Stored variable | Meaning |
duration | ag_Clock | handleEvents timer |
next | ag_delay | the delay of the scheduler |
every | ag_millis | the value returned by millis function |
status | | It must be 0 , it is set to 1 when handleEvents is instantiated |
The events handler class has a set of constants useful for creating an event instance:
EVENT_ENABLED | |
EVENT_AT_TIME | For events occurring at fixed hours |
EVENT_MANUAL | For events activated by program |
day | Is 86400 , i.e., the number of seconds per day |
There are two macros for manage events:
EVENT_ENABLE(event)
EVENT_DISABLE(event)
And also some inline functions for testing the status
:
IS_EVENT_ENABLED
IS_EVENT_ACTIVE
IS_EVENT_ENDING
IS_EVENT_MANUAL
IS_EVENT_AT_TIME
Below is a sample of a set of events:
cronStructure cron[] = {
{2,0,0,EVENT_ENABLED,(fnzPointer) handle_command,"Serial"},
{180,10,60,EVENT_ENABLED,(fnzPointer) handle_command,"Pump"},
{day*1.5,handleEvents::toSeconds("23:59:30"),60,
EVENT_ENABLED|EVENT_AT_TIME,(fnzPointer) handle_command,"Timed B"},
{day/12,handleEvents::toSeconds("0:00:30"),60,
EVENT_ENABLED|EVENT_AT_TIME,(fnzPointer) handle_command,"Timed A"},
{0,0,90,EVENT_MANUAL,(fnzPointer) handle_command,"OnCommand"},
{0,0,0,EVENT_MANUAL,(fnzPointer) handle_command,"pointCmd"}, {0,0,0,0,NULL,NULL} };
How It Works
For every enabled event, the scheduler croner
controls if the next
field is equal or lower of the internal clock; in this case, the associated function is called with parameter cronStructure
event.
After the function call, the next
field is updated depending on the event and the type of event:
- punctual events: the
every
field is added to next
field - events with duration: at the start, the
duration
field is added to the next
field; at the end, the difference from the every
field and the duration
field is added to the next
field
The scheduler returns the time required for the occurrence of the next
event, which can be used to delay the processor.
Tips
Event with Duration
Event with duration is related to a start of device which must work for a prefixed time (or until it is set off); below is a sample of device activation which needs 10 seconds to be ready.
...
{120,50,10,EVENT_ENABLED,(fnzPointer)
handle_Condutt,"Siemens"}, ...
void handle_Condutt(cronStructure &event) {
sprintf(workBuffer,"%s %s ",Time(),event.name);
Serial.print(workBuffer);
if (IS_EVENT_ACTIVE(event)) {
digitalWrite(10,HIGH); Serial.println("enabled");
} else {
int conducibilita=(analogRead(4));
digitalWrite(10,LOW); ...
}
Set and Modify Timer
The time should be set before the activation of loop
cycle by the setTime(seconds)
where seconds
are the seconds from midnight, for example setTime(day/2)
set the timer at twelve o'clock; of course without the setting the time starts at midnight.
It is possible to change the time mainly for imprecision of millis()
; this may have consequences on the management of events, however, the setTime
function changes the time taking into account the days spent and rearranging the time of events not at predefined schedules, i.e., events with the status bit EVENT_AT_TIME
set to 0
. The function addTime(seconds)
modifies the Timer
without effecting the scheduler, it is useful when during the board sleep, it doesn't update the clock.
Repetitive Events on Limited Intervals
The repetitive events which occur at prefixed time for a limited time, for example, to start a pump for one minute every three minutes starting at twelve o'clock and ending after one hour can be handled using two events like in the fragment below:
...
cronStructure cron[] = {
{180,10,60,0,(fnzPointer) handle_pumps,"Pump"},
{day,43200,3600,EVENT_ENABLED|EVENT_AT_TIME,(fnzPointer) handle_StartPump,""},
...
{0,0,0,0,NULL,NULL} };
handleEvents events(cron);
...
void handle_StartPump(cronStructure &event) {
if (!IS_EVENT_ENDING(event)) {
EVENT_ENABLE(cron[events.searchEvent("Pump")]);
} else {
EVENT_DISABLE(cron[events.searchEvent("Pump")]);
}
}
void handle_pumps(cronStructure &event) {
sprintf(workBuffer, "%s %s %s", events.Time(),event.name,
IS_EVENT_ENDING(event)?"ended":"started");
Serial.println(workBuffer);
delay(150);
}
Enable and Disable Manual Events
Enabling a manual event is done simply by setting the state of event to EVENT_ENABLED
; to disable, we must instead the end by setting the next
field to the current time.
...
cronStructure cron[] = {
{0,0,30,EVENT_MANUAL,(fnzPointer) handle_command,"OnCommand"},
...
{0,0,0,0,NULL,NULL} };
handleEvents events(cron);
...
iEvent = events.searchEvent((char *) "OnCommand");
if (!IS_EVENT_ENABLED(cron[iEvent])) {
EVENT_ENABLE(cron[iEvent]);
Serial.println("OnCommand enabled");
} else {
cron[iEvent].next = events.clocker(false); Serial.println("OnCommand disabled");
}
...
Order of Events
To avoid the overlap of repeating events, they can be activated in a staggered manner by acting in the field next; for example, the two following events are activated at every minute but the second starts delayed by 10 seconds.
...
cronStructure cron[] = {
{60,0,0,EVENT_ENABLED,(fnzPointer)
handle_timelyEvent,"timelyEvent"}, {60,10,10,EVENT_ENABLED,(fnzPointer) handle_durationEvent,
"durationEvent"},
{0,0,0,0,NULL,NULL}
}
Other Events Type
An event which must occur on starting can be declared normally, and this must be disabled in the function that manages it, see the example:
...
{0,5,0,1,(fnzPointer) handle_command,"Scan"}, ...
void handle_command(cronStructure &event) {
EVENT_DISABLE(event);
Serial.Println("Arduino started");
}
...
The Demo and Other
SimCron is a sketch which shows the use of handleEvents
with a set of significant events:
- Delayed starting event
- Recurrent events
- Timed event with different frequency
- Manual commands
The sketch can be managed via serial interface substantially for investigating the status, activating manual event and change the timer.
In the downloaded file, you can find:
- Simcron sketch (the demo)
- A sketch for entering Arduino in IDLE mode
- A sketch for entering Arduino in
POWER_DOWN
mode - A Powershell script for seeing Arduino serial (without restarting the card)
- The "executable" of the Powershell script
- The documentation
History
- 29th March, 2015: Initial version
- 16th September, 2020: Article updated