Arduino Scheduler - CodeProject

:

Introduction

This article is the result of a work with Arduino card for control an experimental fish factory integrated with a hydroponic cultivation, developed by SERMIG in order to promote poor people emancipation.

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

// 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 board: 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 the functions for handling the events.

Background

The reader must have some knowledge on how a program running in the automation cards 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.
Other possibilities, all easily achievable as variants of those listed above, like at the beginning or after a period of time.

The Class handleEvents

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[] = {	// the events set
		{...},					// some event
		...
		{0,0,0,0,0,NULL,NULL}	// terminator
	};
	handleEvents events(cron);	// instantiate the object events
	...
	events.setTime(day/2);		// set time at twelve o'clock
	// the functions for handle events
	...
void setup() {
	...
}
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() Give the time and day elapsed in seconds, being unsigned long, it can reach 136 years:
numbers of days = clocker()/86400
seconds from midnight = clocker()%86400
setTime(time of day in seconds) Set the time adding the value to a 00:00:00 of the same day; it can accept a number of seconds greater of a day or negative
listEvents(cronStructure) Shows the events (i.e. the actual status)
Time() Shows the time in the form HH:MM:SS+DD

The events must be arranged on array, with a NULL event terminator:

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

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 {
    unsigned long every;     	// every
    unsigned long next;	    	// next event
    unsigned long duration;  	// for start and stop events
    char status;    		// 1 start, -1 end, 0 disabled
    char command;   		// 0 at command, 1 scheduled, -1 scheduled at time
    fnzPointer fnzActions;
    const char* name;
    unsigned long nextOrig;	// the next original
};

Every time is expressed in seconds:

  • every: This is the interval between two consecutive occurrence of the event, 86400 means 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 command 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:
  • command: This flag indicates the type of the event namely at every defined interval, at prefixed time of day or on command;
  • action: The function which handles the event;
  • name: The event name.
  • nextOrig: This is an internal field.

The events handler class has a set of constants useful for creating an event instance:

EVENT_ENABLED status and command
EVENT_DISABLED status
EVENT_END status This is the status of an event with duration during his activities;
EVENT_ATTIME command For events occurring at fixed hours;
EVENT_MANUAL command For events activated by program.
day   Is 86400, i.e., the number of seconds per day.

Below is a sample of a set of events:

cronStructure cron[] = {
	{2,0,0,EVENT_ENABLED,EVENT_ENABLED,(fnzPointer) handle_command,"Serial"},
	{180,10,60,EVENT_ENABLED,EVENT_ENABLED,(fnzPointer) 
		handle_command,"Pump"},  // starts 10 seconds after program start
	{day*1.5,toSeconds("23:59:30"),60,EVENT_ENABLED,
		EVENT_ATTIME,(fnzPointer) handle_command,"Timed B"},
	{day/12,toSeconds("0:00:30"),60,EVENT_ENABLED,
		EVENT_ATTIME,(fnzPointer) handle_command,"Timed A"},
	{0,0,90,EVENT_DISABLED,EVENT_MANUAL,(fnzPointer) 
		handle_command,"OnCommand"},	// start on command end after 90 seconds
	{0,0,0,EVENT_DISABLED,EVENT_MANUAL,(fnzPointer) 
		handle_command,"pointCmd"},  	// start on command
	{0,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 command type:

  • Manual events: The next field is set to the actual time plus the every field.
  • Punctual events: The every field is added to next field.
  • Duration events: 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,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 (event.status == EVENT_ENABLED) {
    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 related to predefined schedules.

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 four hours can be handled using two events like in the fragment below:

...
cronStructure cron[] = {
        {180,10,60,EVENT_DISABLED,EVENT_ENABLED,(fnzPointer) handle_command,"Pump"},
        {day,43200,4*3600,EVENT_ENABLED,EVENT_ATTIME,(fnzPointer) handle_command,"Timed"},
        {0,0,0,0,0,NULL,NULL}   // terminator
};
handleEvents events(cron);
...
void handle_command(cronStructure event) {
	if (event.name == "Timed") {
		if (event.status == EVENT_ENABLED) events.enableEvent("Pump");
		else cron[events.searchEvent("Pump")].status = EVENT_DISABLED;
	} else {
		...
	}
}
...

Enable and Disable Manual Events

Enabling a manual event is done simply by setting the state of event to EVENT_ENABLED; to disable is not sufficient sets the status field of event to EVENT_DISABLED, we must instead force the end by setting the next field to the current time; if this event precedes the event to disable, it will be immediately executed.

...
cronStructure cron[] = {{2,0,0,EVENT_ENABLED,EVENT_ENABLED,(fnzPointer) handle_command,"Serial"},
...
        {0,7,20,EVENT_ENABLED,EVENT_MANUAL,(fnzPointer) handle_command,
		"OnCommand"},	// start on command end after 90 seconds
...
        {0,0,0,0,0,NULL,NULL}   // terminator
};

handleEvents events(cron);
...
	iEvent = events.searchEvent((char *) "OnCommand");
	eStatus = cron[iEvent].status;
	if (eStatus == EVENT_DISABLED) {
		cron[iEvent].status = EVENT_ENABLED;
	} else {
		cron[iEvent].next = events.clocker();		// force close
	}
...

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,EVENT_ENABLED,(fnzPointer)
    handle_timelyEvent,"timelyEvent"},        // activate every minute
    {60,10,10,EVENT_ENABLED,EVENT_ENABLED,(fnzPointer) handle_durationEvent,
    "durationEvent"},  // activate every minute, starts 10 seconds after start event duration 10 seconds
    {0,0,0,0,0,NULL,NULL}

Other Events Type

An event which must occur on starting can be declared normally, and the event must be disabled in the function which handles this, see the example:

...
{0,5,0,1,0,(fnzPointer) handle_command,"Scan"},    // starts 5 seconds after start
...
void handle_command(cronStructure event) {
	event.status = EVENT_DISABLED;
	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 and activating manual event; besides it can change the timer and switch from delay to sleep mode.

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