Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / IoT / Arduino

Arduino Scheduler

4.81/5 (8 votes)
16 Sep 2020CPOL7 min read 34.1K   729  
A function that works on event of Arduino and ESP32 boards
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.

Image 1

Image 2

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):

C++
// includes, defines and everywhere accessible variables
void setup() {
  // put your setup code here, to run once:
}
void loop() {
  // put your main code here, to run repeatedly:
}

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:

  1. at every defined time interval
  2. at prefixed time of day
  3. 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:

C++
...
cronStructure cron[] = {                // the events set
		{...},                          // some events
       {0,0,0,0,NULL,NULL}   	        // terminator
};
handleEvents events(cron);              // instantiate the object events
...
events.setTime(day/2);                  // possibly set time (at twelve o'clock)
// here the functions for handle events
...
void setup() {
...
// events.begin(cron, pw_Clock);        // needed if ESP32 can go in deep sleep
...
}
void loop() {
        delay(events.croner(cron));     //  handleCronEvents
}

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).

C++
typedef void (* fnzPointer) (struct cronStructure *x);
struct cronStructure {
    uint32_t every;    // every seconds
    uint32_t next;     // next event
    uint32_t duration; // for start and stop events (in seconds)
    int8_t status;     // if 1 bit 1: enabled, bit 2: active, 
                       // bit 3: active ending, bit 4: manual, bit 5: at time
    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:

C++
cronStructure cron[] = {{...},{0,0,0,0,NULL,NULL}};

or:

C++
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:

C++
cronStructure cron[] = {
        {2,0,0,EVENT_ENABLED,(fnzPointer) handle_command,"Serial"},
        // Pump event starts 10 seconds after program start
        {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"},
        // OnCommand event start when it is activate and it is recalled after 90 seconds
        {0,0,90,EVENT_MANUAL,(fnzPointer) handle_command,"OnCommand"},
        {0,0,0,EVENT_MANUAL,(fnzPointer) handle_command,"pointCmd"},  // start on command
        {0,0,0,0,NULL,NULL}                                           // terminator
};

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.

C++
...
	{120,50,10,EVENT_ENABLED,(fnzPointer) 
		handle_Condutt,"Siemens"},  // at start active the sensor
...
void handle_Condutt(cronStructure &event) {
  sprintf(workBuffer,"%s %s ",Time(),event.name);
  Serial.print(workBuffer);
  if (IS_EVENT_ACTIVE(event)) {
    digitalWrite(10,HIGH);     // enabled
    Serial.println("enabled");
  } else {
    int conducibilita=(analogRead(4));
    digitalWrite(10,LOW);  // disabled
...
}

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:

C++
...
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}   // terminator
};
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.

C++
...
cronStructure cron[] = {
	{0,0,30,EVENT_MANUAL,(fnzPointer) handle_command,"OnCommand"}, 
...
	{0,0,0,0,NULL,NULL}   // terminator
};
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);		// force close
        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.

C++
...
cronStructure cron[] = {
    {60,0,0,EVENT_ENABLED,(fnzPointer)
    handle_timelyEvent,"timelyEvent"},    // active every minute
    // active every minute, starts 10 seconds after start
    {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:

C++
...
{0,5,0,1,(fnzPointer) handle_command,"Scan"},    // starts 5 seconds after start
...
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:

  1. Delayed starting event
  2. Recurrent events
  3. Timed event with different frequency
  4. 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:

  1. Simcron sketch (the demo)
  2. A sketch for entering Arduino in IDLE mode
  3. A sketch for entering Arduino in POWER_DOWN mode
  4. A Powershell script for seeing Arduino serial (without restarting the card)
  5. The "executable" of the Powershell script
  6. The documentation

History

  • 29th March, 2015: Initial version
  • 16th September, 2020: Article updated

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)