04 May 2012

Setting Alarms in Harmattan (Nokia N9)

I have decided to kick start my blogging with a topic not very well covered by the 'official' Meego Harmattan documentation: setting alarms.

The sources of this post are timed documentation only available online here and the timed's simple-client source code. You will have to look at those to do anything more complex than the topics I cover.

Headers and Compilation


You are going to need these headers:
#include <timed/interface>
#include <timed/event>

And to add timed to CONFIG in the project file:
CONFIG += timed

If you are building in Scratchbox (aka Platform SDK) also add libtimed-dev to the Build-Depends in debian/control file.

Simple Event with Alarm


This is pretty easy so first the code:

Maemo::Timed::Event event;
event.setAttribute("APPLICATION", "alarmtest");
event.setAttribute("TITLE", "Get up now!");
event.setAttribute("PLUGIN", "libclockalarm");
event.setBootFlag();
event.setAlarmFlag();
event.setReminderFlag();
// set the alarm in 10s from now
event.setTicker(QDateTime::currentDateTime().toTime_t() + 10);


And some brief explanations:
  • APPLICATION attribute is mandatory and the string has the same rules as C identifiers (alphanumeric and underscore characters only, first character not a number). If you fail these rules no error is reported but the returned cookie is zero and no event is added.
  • TITLE attribute is the title of the alarm (in this case) and will be shown on screen.
  • PLUGIN attribute is the plugin used when the event is triggered. You can write your own plugin but here we are using the one for alarms.
  • setBootFlag() - will ring even if device is off i.e. boots the device if needed.
  • setAlarmFlag() - tells the system it's an alarm not only a scheduled task.
  • setReminderFlag() -  needed to show the default alarm dialog.
  • setTicker() - sets the alarm time in seconds (there are a lot of other 'more human' ways to set this).

Now you just need to add the event to timed's queue. Here is some sample code with error checking:

// timed interface setup
Maemo::Timed::Interface timedIface;
if(!timedIface.isValid()) {
   qDebug() << "Invalid timed interface:" << timedIface.lastError();
   return 1;
}
// add the event
QDBusReply<uint> reply = timedIface.add_event_sync(event);
if(!reply.isValid()) {
   qDebug() << "Adding event failed:" << reply.error().message();
   return 1; 
}
qDebug() << "Added event with cookie:" << reply.value();

Note that you get back a 'cookie' which is in fact the event identifier. This can be later used to search, cancel or change the event.

Event with Command Execution


To do anything more than setting the alarm we need an Action object. Here we are running a command when the user presses the alarm Stop button:

Maemo::Timed::Event::Action &act = event.addAction();
act.setSendCookieFlag();
act.runCommand("echo Event cookie <COOKIE> $(date) >> /tmp/test");
act.whenSysButton(2); // 1 - snooze, 2 - stop

setSendCookieFlag() will cause timed to replace 'COOKIE' or '<COOKIE>' in the command string with the event cookie value.

If you need to run a graphical application there are two other issues to consider. First you need some environment variables set: DISPLAY=:0 and DBUS_SESSION_BUS_ADDRESS (source it from /tmp/session/bus_address.user). Second you need to make sure the application is run as user: set the UID and GID tokens to user/users in aegis manifest file for your application path. Probably the best solution here is to use a wrapper script to start the application.

Also note that timed is a crond replacement so you can schedule tasks to run in the background without user interaction. Recurrences can be done with the Maemo::Timed::Event::Recurrence class.

Cancel and Replace Events


There are 2 very simple API calls for canceling and replacing events based on their cookies:

bool cancel_sync(uint cookie);
uint replace_event_sync(Maemo::Timed::Event newEvent, uint cookie);