How to program the Pebble smartwatch: Part 2

Update Pebble has released version 2 of its OS and this invalidates much of what follows, which was written for an earlier version of the OS.

In Part 1 we got our basic Pebble app up and running, but it doesn’t do very much. Let’s add some user interaction.

To respond to button presses, Pebble OS now uses a system akin to its event handling mechanism, the better to help the coder give the user more ways to control the three-button watch. The new approach lets you directly accommodate single clicks short and long, double-clicks, and press-and-hold events, rather than simply waiting for a push on a specific button and then trying to anticipate the user’s intentions.

The Pebble SDK, then, defines a ClickConfigProvider entity which is essentially an array of function calls for specific buttons and the various ways each of them can be used. This list of calls is attached to the host window. First, we need to add the line

    window_set_click_config_provider(&window, (ClickConfigProvider)config_provider);

to the handle_init() initialisation function, and we need to run it after the app’s Window – reached using the pointer variable window – has been pushed onto the OS’s Window stack, or it will be ignored. The above line tells the window where to get its array of button configurations from, which it does by calling a second function, the config_provider passed in the first call.

Here’s what it looks like in our app:

void config_provider(ClickConfig **config, Window *window)
    config[BUTTON_ID_UP]->click.handler = (ClickHandler)up_single_click_handler;
    config[BUTTON_ID_DOWN]->click.handler = (ClickHandler)down_single_click_handler;

We punch into the array handlers for the button-related events we’re interested in. Here, that’s a couple of single-click handlers, added to the click.handler, fields, but we could have added function calls for each button’s multi_click.handler, its long_click.handler and/or its long_click.release_handler fields. Other fields specify how many clicks in a group we’re interested in, or whether we’re only interested in the final click in a batch – it doesn’t matter how many times the user presses the button in rapid sequence, we just deal with the last one. The mechanism can handle hold-to-repeat actions too.

With your handlers registered this way, all you have to do now is write the handlers themselves. This app uses the top and bottom buttons to trigger a sudden change in the bouncing ball’s direction of movement:

void up_single_click_handler(ClickRecognizerRef recognizer, Window *window)
    delta_x = delta_x * -1;


void down_single_click_handler(ClickRecognizerRef recognizer, Window *window)
    delta_y = delta_y * -1;

These handlers and, indeed, the config_provider function require declarations at the top of the file, or in a separate header file if you’re using one.

Updating the ball’s movement every second doesn’t make for a very dynamic display, of course. As I noted in Part 1, Pebble OS’ .tick_info handler doesn’t generate more than one event a second, so we’ll have to use its timer_handler if we want to update the screen more frequently than that. To do so, edit the pbl_main function, to remove the .tick_info section from the list of PebbleAppHandlers and replace it with .timer_handler = &handle_timer. The function should now look like this:

void pbl_main(void *params)
    AppContextRef ctxt = (AppContextRef) params;
    PebbleAppHandlers handlers =
        .init_handler = &handle_init,
        .deinit_handler = &handle_deinit,
        .timer_handler = &handle_timer
    app_event_loop(ctxt, &handlers);

You’ll notice that I’ve also added a second new handler, .deinit_handler, which is called when the app quits, and is here used to politely cancel any timers in the event queue that have yet to fire:

void handle_deinit(AppContextRef ctxt)
    app_timer_cancel_event(ctxt, timerHandle);

The value timerHandle is a global variable declared at the start of the program alongside Window window. It provides a reference to the AppTimerHandle timer in memory. We’ll set this up in the handle_init() with an extra line at the end of that function:

    timerHandle = app_timer_send_event(ctxt, 500, 1);

It takes the usual AppContextRef pointer to the app itself, a time in milliseconds before which the timer fires – half a second here – and a unique integer, called a “cookie” by the SDK, to identify the specific timer to the handler when you’re running more than one at once. The timer’s handle and its cookie value are passed to the timer handler function when it’s triggered, so you can check which timer has triggered the code and respond accordingly.

Delete the handle_tick() function if you like, but copy it first to form the basis for your handle_timer() function:

void handle_timer(AppContextRef ctxt, AppTimerHandle handle, uint32_t cookie)
    pos_x = pos_x + delta_x;
    pos_y = pos_y + delta_y;
    if (pos_x > 140)
        pos_x = 132;
        delta_x = -8;
    if (pos_x < 4)
        pos_x = 12;
        delta_x = 8;
    if (pos_y > 162)
        pos_y = 154;
        delta_y = -8;
    if (pos_y < 4)
        pos_y = 12;
        delta_y = 8;
    timerHandle = app_timer_send_event(ctxt, 100, 1);
    Layer *root = window_get_root_layer(&window);

One extra line goes in, to call the app_timer_send_event() function as per the line in handle_init(). As yet, Pebble OS timers don’t fire continuously, so we add a new one to the event queue each time the timer fires. I’ve set this time to 100ms.

Compile the app, transfer it to your watch, select it from the Pebble’s menu and you should see a ball moving at a moderate pace around the screen. You can change its direction with a press of the top or bottom buttons. Not very impressive, of course, but the code I’ve outlined here – handling button presses and scheduling evens – can form the basis for much more interesting and more useful apps.

The app’s graphics are particularly crude, so I’ll address that in Part 3 by taking a look at how you can add graphics and other resources to Pebble apps.

An edited version of this article originally appeared in The Register


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s