Events & Calendars

This is part of the Chronos embedded calendar event library tutorial and API docs.

Events are entities that take up space on the timeline: they have at least one occurrence in time where they have a definite beginning and end. These may be “one-shot”, single occurrence events that happen once (like “Bob’s wedding, Sunday 5th June 2016, from 15h00 to 21h00“), or can be recurring (“Jiujitsu class, every Saturday 9h00-10h00“).

Scheduled events may be created a few ways, depending on how you want the event to behave. You may create Chronos::Events using

The first two are alternative ways to specify a one-time event. The third manner, using some Chronos::Mark, will setup a repeating event (daily, weekly, monthly, or yearly depending on the type of mark used).

Normally, the objects are just created on the fly during the call to add to the calendar:

	// an one-shot meeting next June
	MyCalendar.add(
	   Chronos::Event(LONG_MEETING_ID, 
	      Chronos::DateTime(2016, 6, 21, 16, 00),   // June 21st 4pm
	      Chronos::DateTime(2015, 6, 21, 19, 30))); // to 7:30pm

and a repeating:

	// a meet on the 1st of every month, 6pm-8pm
	MyCalendar.add(
	   Chronos::Event(CONDO_ASSOC_MONTHLY_ID, 
	      Chronos::Mark::Monthly(1, 18, 0, 0),
	      Chronos::Span::Hours(2)));

If you have only a single event of interest, you can hold on to it and use its API directly but for the most part you’re probably better off using the Calendar, even in this case.

The Chronos calendar is just a container for these events and an interface to perform useful queries on the set of events. Let’s say you want to control two lights, turning one off every night a 6pm and back on at 7am, and the other should be off between midnight and 7am. The simplest implementation is to define recurring daily events to turn the lights off.

Let’s say we’ve just powered up. To start, we have three jobs (setup the timekeeping, create our calendar, add our events):

	// setup our clock source and arrange to get the real time and date
	// with the Time library, using its "setSyncProvider()" function:
	setSyncProvider(mySyncFunction); // however you've set it up...

	// define a calendar type "Calendar"
	DefineCalendarType(Calendar, 2); // to hold only 2 events

	// and *then* declare a global using that 
	// new type ("Calendar", here) to hold everything:
	Calendar MyCalendar;


	// some defines to keep things legible
	#define LIGHTS_OFFICE_OFF_ID	0
	#define LIGHTS_ENTRANCE_OFF_ID	1

	// now add that daily lights-off events
	// office lights
	MyCalendar.add(
	   Chronos::Event(LIGHTS_OFFICE_OFF_ID,
	      Chronos::Mark::Daily(18,0,0), // off at 18h00
	      Chronos::Span::Hours(13))); // for 13 hours
	
	// entrance lights
	MyCalendar.add(
	   Chronos::Event(LIGHTS_ENTRANCE_OFF_ID,
	      Chronos::Mark::Daily(0,0,0), // off at midnight
	      Chronos::Span::Hours(7))); // for 7 hours

Now we’re all setup. The first question is: should the lights be on or off, right this minute:

	// we create an array to hold occurrences
	Chronos::Event::Occurrence	occurrenceList[2]; 
	// and a list to tell us which lights to turn off
	bool turnOff[2] = {false, false};

	// ask what's happening right now: listOngoing
	// returns the number of on-going events set in 
	// return array.
	uint8_t numOngoing = 
	   MyCalendar.listOngoing(2, // return maximum 2 occurrences
                                  occurrenceList, // into this array
                                  Chronos::DateTime::now()); // for "right now"

	for (uint8_t i=0; i

If this is the only job our microcontroller needs to handle, then we are done until the next time something happens. Here "something happens" means one of:

  1. Some currently on-going event comes to an end; or
  2. Some future event begins

We don't really care which eventually this may be because, whichever it is, we'll need to do something--either turn a light on or off. In this case, the simplest thing to do is ask the calendar when the next datetime of interest will be:

	// create a date-time to hold the result:
	Chronos::DateTime canSleepUntil;

	// ask the calendar 
	if (! MyCalendar.nextDateTimeOfInterest(Chronos::DateTime::now(), canSleepUntil) )
	{
	   // well, we should _never_ get here, since our events are repeating and 
	   // a 'next' event is therefore always available... but nextDateTimeOfInterest()
	   // can, in theory, return false--in the specific case where all calendar events
	   // configured are **one-time** events and they are all in the past, relative 
	   // to the base time passed in (the first parameter)

	   dieHorribly("aaaaagh! No next DT of interest???");
	}


	// and now we go to sleep until we have more work:
	sleepMyMCUFor(  (canSleepUntil - Chronos::DateTime::now()).totalSeconds() );

	// ah, that was a nice nap.  Now that we're awake again, we know something has changed...
	// deal with it: make sure lights are as they should be then lather, rinse, repeat.

There are other ways of doing this, of course.

For instance, I used "lights off" events, though "lights on" might be more natural and would invert the logic a bit... it's a question of preference and the fact that lights-off shows that you don't have to care about day-boundaries (lights will be off from 18 to 7, as easily as from 7 to 18, without worrying about signage or date bounds).

Most of the calendar methods are either related to adding/removing events or listing event occurrences in a manner similar to listOngoing() shown above.

The list*() methods all use arrays of Chronos::Event::Occurence objects to return results, so you can decide how many you want (and thereby how much memory to devote to the task).


An event occurrence is a specific instantiation of an event in "real life". For one-time events this pretty much equivalent to the Chronos::Event definition itself but for repeating events, an Occurrence is just one specific time at which the abstract ("Sundays from 11-12") actually happens ("Sunday June 26th 2016 11:00" - "Sunday June 26th 2016 12:00").

Chronos::Event::Occcurence objects act like simple structs with four attributes:

  • id: EventID, which lets you know which event the occurrence refers to;
  • start: DateTime object of when event begins;
  • finish: DateTime object representing final moment of event; and
  • isOngoing: boolean, true if event is actually on-going at querie's base time (start <= baseDateTime <= finish)

Note that these Event::Occurrence arrays are used for temp storage and manipulation in the call, so if you pass a 10 element array to listNext(), which returns a value of, say, 3 (meaning 3 occurrences found and added to the array) then you now that now:

  • indicies 0, 1 and 2 have event occurrences of interest; and
  • any index beyond 2 ( meaning [3,9]) is invalid and may have been changed by the call.

Sample Code

There's a functional sample program exercising the library functionality, included with the library.

Scheduled Event API

The Chronos::Event API is, under normal circumstances, limited to construction of the events for adding to the Calendar.

Construction

All events need an Chronos::EventID, which should be a positive integer but need not be unique (though it probably should be). This id will be available as the id attribute of the Event::Occurrences returned by the various calendar list*() calls.

	Event(Chronos::EventID id, const DateTime & start, const DateTime & end);

Create an event using beginning and ending DateTimes (inclusive, meaning the event is still on-going at "end" and then finished one second later).

@param id: EventID for this event
@param start: DateTime at which event begins
@param end: DateTime at which event ends

	Event(EventID id, const DateTime & start, const Chronos::Span::Delta & duration);

Create an event using a start DateTimes and a duration.

@param id: EventID for this event
@param start: DateTime at which event begins
@param duration: a Chronos::Span to set how long it lasts, e.g. Chronos::Span::Minutes(30)

	Event(EventID id, const Chronos::Mark::Event & timeEvent, const Chronos::Span::Delta & duration);

Create an event using a repeating time mark and a duration.

@param id: EventID for this event
@param mark: a time mark for the event start, e.g. Chronos::Mark::Weekly(Chronos::Weekday::Monday)
@param duration: a Chronos::Span to set how long it lasts, e.g. Chronos::Span::Hours(2)

Calendar API

Construction

Construction is simply a matter of declaring your calendar. The current implementation uses templating to define a static array of the correct size. The usual method is to create an alias (just a typedef) of the correct size using:

	// Create your own calendar class of the correct (max) size using
	// DefineCalendarType(A_CLASS_NAME, MAX_NUM_EVENTS_TO_HOLD)
	DefineCalendarType(Calendar, 10);

and then to use that new class name to declare your calendar:

	// and *then* declare a global using that new type 
	// ("Calendar", here) to hold everything:
	Calendar MyCalendar;

Adding and removing events

For a calendar to be useful, some events need to be added (see discussion above or the Calendar.ino example). The relevant methods are:

	bool add(const Chronos::Event & event);

@param event: the Chronos::Event to add
Returns true if there was enough room in the calendar for this event.

	bool remove(EventID eventId);

@param eventId: the EventID to search for
Returns true if event was found and removed

	void clear();

Empty the calendar of all events add()ed.

	uint8_t numEvents()

Returns number of events setup in calendar.

	uint8_t numRecurring()

Returns number of events setup that are repeating events (created using a Chronos::Mark::*) numRecurring() will always be <= numEvents().

Listing event occurrences

	uint8_t listOngoing(uint8_t maxNumber, Event::Occurrence intoArray[], const DateTime & dt) ;

Fill a list (up to maxNumber) with events that are actually happening at a specific DateTime.

@param maxNumer: maximum number return array can hold
@param intoArray: Event::Occurrence array to hold return values, see discussion above
@param dt: DateTime of interest

Returns number of event occurrences actually loaded into intoArray

NOTE: At return, Occurrences [0, returnValue] will be set in intoArray, and sorted by start DateTime

	uint8_t listNext(uint8_t maxNumber, Event::Occurrence intoArray[], const DateTime & dt);

Fill a list (up to maxNumber) with events that will happen starting after DateTime dt. Events returned may be any amount of time into the future.

@param maxNumer: maximum number return array can hold
@param intoArray: Event::Occurrence array to hold return values, see discussion above
@param dt: DateTime of interest
Returns number of entries loaded into intoArray

NOTE: At return, Occurrences [0, returnValue] will be set in intoArray, and sorted by start DateTime

	uint8_t listForDay(uint8_t maxNumber, Event::Occurrence intoArray[], const DateTime & dt);

List all events that will begin on the day specified in dt.
@param maxNumer: maximum number return array can hold
@param intoArray: Event::Occurrence array to hold return values, see discussion above
@param dt: DateTime of interest
Returns number of entries loaded into intoArray

NOTE: At return, Occurrences [0, returnValue] will be set in intoArray, and sorted by start DateTime

	bool nextDateTimeOfInterest(const DateTime & fromDT, DateTime & returnDT);

Get the next DateTime "of interest" -- meaning the closest future DateTime at which any calendar event will end or begin.

@param dt: base DateTime from which the relative current/next events are calculated
@param returnDT: a DateTime that will be set by the method with our result, if found.

Returns boolean true if the value in returnDT actually contains a valid "next datetime of interest" when the call returns.