Pebble SDK 2.0 Tutorial #5: Buttons and Vibrations

Required Reading

Pebble SDK 2.0 Tutorial #1: Your First Watchapp

Pebble SDK 2.0 Tutorial #2: Telling the Time

Pebble SDK 2.0 Tutorial #3: Images and Fonts

Pebble SDK 2.0 Tutorial #4: Animations and Timers

Introduction

In this section of the tutorial we will be returning back to basics, building a simple watchapp that will use button presses (or clicks) and vibrations to enable the user to give input and receive output.

To get started, make a new CloudPebble project and add the C file from section 1, which consists of just the basic app life-cycle functions and a TextLayer. Since it is so brief, here it is again in full (with a couple of refinements for clarity):

#include <pebble.h>

Window* window;
TextLayer *text_layer;

/* Load all Window sub-elements */
void window_load(Window *window)
{
	text_layer = text_layer_create(GRect(0, 0, 144, 168));
	text_layer_set_background_color(text_layer, GColorClear);
	text_layer_set_text_color(text_layer, GColorBlack);

	layer_add_child(window_get_root_layer(window), (Layer*) text_layer);
	text_layer_set_text(text_layer, "My first watchapp!");
}

/* Un-load all Window sub-elements */
void window_unload(Window *window)
{
	text_layer_destroy(text_layer);
}

/* Initialize the main app elements */
void init()
{
	window = window_create();
	WindowHandlers handlers = {
		.load = window_load,
		.unload = window_unload
	};
	window_set_window_handlers(window, (WindowHandlers) handlers);
	window_stack_push(window, true);
}

/* De-initialize the main app elements */
void deinit()
{
	window_destroy(window);
}

/* Main app lifecycle */
int main(void)
{
	init();
	app_event_loop();
	deinit();
}

Now you’re back up to speed, it’s time to add the first new element: button clicks. The way this works in the Pebble SDK is that you provide the system with callbacks for what you want to happen when the button is pressed, just like with a TickTimerService implementation. These callbacks have the following signatures:

void up_click_handler(ClickRecognizerRef recognizer, void *context)
{

}

void down_click_handler(ClickRecognizerRef recognizer, void *context)
{

}

void select_click_handler(ClickRecognizerRef recognizer, void *context)
{

}

These will be needed in init() so make sure to place them above that function in the source file. To keep areas of code separate, place them above the window_load() and counterpart function to keep all Window related functions in one place in the file.

We will leave these blank for now as we continue to put all the pieces in place required for button click functionality. The next step is to register these with the system so it knows what to do when the button clicks occur. This is done in another function called a ClickConfigProvider, which (you guessed it) provides the click configuration. It looks like this, when filled with the requisite function calls necessary to register the individual button press callbacks from earlier. Each call links a button to its callback. Hopefully you can read it easily:

void click_config_provider(void *context)
{
	window_single_click_subscribe(BUTTON_ID_UP, up_click_handler);
	window_single_click_subscribe(BUTTON_ID_DOWN, down_click_handler);
	window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler);
}

After creating the button callbacks and providing a mechanism for telling the system what each individual button will do when pressed, the final step is to provide the system with the click_config_provider() function to enable it to call it and set up the button click behaviors. The back button cannot be controlled by the developer as it is used to back out a watchapp to the system menu! This final step is achieved in init() after the Window is created (but before it is pushed!) like so:

window_set_click_config_provider(window, click_config_provider);
window_stack_push(window, true);

Now we have our button clicks registered, let’s make them do something! Perhaps the simplest and easiest demonstration is to have the buttons change the text being shown by the TextLayer. First, change the prompt shown to the user in window_load() from “My first watchapp!” to something a bit more relevant, such as “Press a button!”. Now, in each button click handler callback function, add another text_layer_set_text() function call to set the text shown to that particular button. Here is just one example (do the other two yourself in a similar fashion!):

void up_click_handler(ClickRecognizerRef recognizer, void *context)
{
	text_layer_set_text(text_layer, "You pressed UP!");
}

After adding some actions to the three callbacks, compile the watchapp (make sure it is actually a watchapp as dictated by ‘App kind’ in the Settings screen) and test it. It should look like this:

pressed

So, there you have button clicks. To change the behavior, just change what happens in the callback functions. The rest can stay the same.

Vibrations
With buttons providing a means of user input to your app, the next main means of output, besides what is being displayed on the screen, is to use the built-in vibration motor to notify users to events. For example, in my Pebble Tube Status app (another shameless plug!) the watch vibrates once the updates data has been sent to the watch, so in the case of a slow data connection, the user can ignore the watch until the information is ready for viewing.

To use this functionality is much simpler than anything else we’ve covered so far. You can make the watch vibrate simply with one single line:

vibes_short_pulse();

/* or */

vibes_long_pulse();

/* or */

vibes_double_pulse();

To initiate a more complex vibration sequence, use a different form as shown below (I placed this in up_click_handler(), for example):

//Create an array of ON-OFF-ON etc durations in milliseconds
uint32_t segments[] = {100, 200, 500};

//Create a VibePattern structure with the segments and length of the pattern as fields
VibePattern pattern = {
	.durations = segments,
	.num_segments = ARRAY_LENGTH(segments),
};

//Trigger the custom pattern to be executed
vibes_enqueue_custom_pattern(pattern);

Conclusion
That’s pretty much all there is to button clicks and vibrations, which wraps up this part of the tutorial.

You can find a link to the full source code resulting from what we’ve covered here on GitHub.

Next up: an introduction to working with PebbleJS and AppMessage. Basic knowledge of JavaScript required!

Advertisements
61 comments
  1. Johnny said:

    Thank you for this! The source code link is not working.

    • bonsitm said:

      Hi Johnny, it looks like Dropbox went down overnight. Should be back up soon.

    • bonsitm said:

      Johnny, I have moved the code to GitHub and it is now available.

      • Johnny said:

        Thank you!

  2. Johnny said:

    On this Project …pebble-watch-face-sdk-tutorial-6-2-way-communication-with-android/… you comunicate with android app. How does the android different from then and now with this example?

    • bonsitm said:

      A few things have changed since then. Fewer calls/different calls, different setup for AppMessage for example. I’ll be covering that topic soon.

  3. Hi, I have one question, how I can make to use the up and down button to scroll the text layer in my pebble app and the select one of the text layer.

    • bonsitm said:

      Yes, these two can be merged. In the received data callback be sure to call menu_layer_reload_data() to show the new data.

  4. bonsitm
    can you tell me which can be the right way to bring the strings to the menu layer. In the AppMessage for PebbleKit JS was really easy to set the text, but in the menu way I cannot do it

    I have tried
    snprintf(question_buffer, sizeof(“Q: couldbereallylongname”), “Q: %s”, string_value);
    text_layer_set_text(menu_cell_basic_header_draw, (char*) &question_buffer);
    but it is not possible

    • bonsitm said:

      The string must be saved in a ‘long-lived’ storage, such as the suggested global char buffers, or a static heap allocated variable.

      You can store the string in the MenuLayer by specifying it as the title or body of any item when the row is drawn in that callback.

      An example is my Pebble Tube Status app, which you can see on my GitHub page.

  5. bonsitm,
    I’ve checked the Tube status, it’s a great example. Why in this project there are no buffers just like in the London Wheather app?
    In your Tube Status app, are u using a long-lived storage or a static heap?

    I am creating an poll app with only options and one question, the string is very simple.
    {“url”: “http://127.0.0.1:8000/apiquestions/1/”, “id”: 1, “question_text”: “Who’s your favorite singer?”, “options”: [“Philip”, “Sandra”, “Lea”]
    https://github.com/apolov/my_socialtv_project/blob/master/src/my_socialtv_project.c
    Can you give me some suggestion to not be stuck with the code?

    Thanks in advance

    • bonsitm said:

      The buffers in Pebble Tube Status are declared on line 43 here. The data is stored on line 98, and drawn in the MenuLayer on lines 125 – 161. I will take a look at your code when I have some free time. Best of luck!

  6. Can I use an option like this
    void draw_row_handler(GContext *ctx, Layer *cell_layer, MenuIndex *cell_index, void *callback_context)
    {
    switch (cell_index->row)
    {
    case OP1:
    menu_cell_basic_draw(ctx, cell_layer, “option 1”, OP1, NULL);
    break;
    case OP2:
    menu_cell_basic_draw(ctx, cell_layer, “option 2”, OP2, NULL);
    break;
    case OP3:
    menu_cell_basic_draw(ctx, cell_layer, “option 3”, OP3, NULL);
    break;
    default:
    menu_cell_basic_draw(ctx, cell_layer, “Unknown”, “This is a bug!”, NULL);
    break;
    }
    }

    With a Javascript dictionary like this
    var getOptions = function() {
    //Get info
    var response = HTTPGET(“http://172.20.64.27:8000/apiquestions/1/?format=json”);

    //Convert to JSON
    var json = JSON.parse(response);

    //Extract the data
    var question = json.question_text;
    var op1 = json.options[0];
    var op2 = json.options[1];
    var op3 = json.options[2];

    //Console output to check all is working.
    //console.log(“It is ” + temperature + ” degrees in ” + location + ” today!”);

    //Construct a key-value dictionary
    var dict = {“KEY_QUESTION” : question, “KEY_OP1”: op1, “KEY_OP2”: op2, “KEY_OP3”: op3};

    //Send data to watch for display
    Pebble.sendAppMessage(dict);
    };

    • bonsitm said:

      This looks OK, provided you are storing the values to the KEY_OP1 etc. keys in the correct buffers, and that OP1 – 3 are these long lived buffers.

  7. Cris, I think I have an issue doing the process tuple and the construction the draw_row_handler. Maybe I’m not storing in the right way the variables or constructing the tuples.
    void process_tuple(Tuple *t)
    {
    //Get key
    int key = t->key;

    //Get integer value, if present
    int value = t->value->int32;

    //Get string value, if present
    char string_value[32];
    strcpy(string_value, t->value->cstring);

    void draw_row_handler(GContext *ctx, Layer *cell_layer, MenuIndex *cell_index, void *callback_context)
    {
    switch (cell_index->row)
    {
    case KEY_OP1:
    menu_cell_basic_draw(ctx, cell_layer, “option 1”, op1_buffer, NULL);
    break;
    case KEY_OP2:
    menu_cell_basic_draw(ctx, cell_layer, “option 2”, op2_buffer, NULL);
    break;
    case KEY_OP3:
    menu_cell_basic_draw(ctx, cell_layer, “option 3”, OP3, NULL);
    break;
    default:
    menu_cell_basic_draw(ctx, cell_layer, “Unknown”, “This is a bug!”, NULL);
    break;
    }
    }
    }

    https://github.com/apolov/success/blob/master/src/success.c
    I’d appreciate if you could give me a hand.

    • bonsitm said:

      Hi again,

      My first thought is that when the data is being received in in_received_handler() you are not storing anything in the buffers you later use for drawing text in the MenuLayer. When you properly store the data it will begin to appear in the correct places.

  8. Yeah actually, I finally constructed the men. But It seems that nothing it is being stored because my first menu it is “Unknow, This is a bug” and it is not taking in time to display like in the London tube or Javascript app
    I think I have a problem with the tuples, but I cannot find the problem.

    • bonsitm said:

      Line 56 creates a Tuple using the key OUTPUT. If you create one for each of your OP keys and use those to get the cstring values you can get each key’s value as they come in.

  9. I’ve decided to use a Tuple *tuple = dict_find(iter, OUTPUT); instead of dict_read_first(iter)
    and a while (tuple !=NULL)
    tuple=dict_read_next(iter);
    but I am still confused about whether is the right way to iter the tuples.

    • bonsitm said:

      I dont think you should use the OUTPUT key because it bears no relevant to your project. Use the same keys as in the JS code to search the received dictionary.

  10. But in that case how I construct the void draw_row_handler or use the key. Until now the void draw_row_handler is using a switch (cell_index->row) but in the AppMessage for PebbleKit JS the switch() use as argument the keys.

    • bonsitm said:

      This is because action to take when receiving data depends on the key representing it. Whereas for drawing the MenuLayer row the variable is which row is being drawn, and the key is irrelevant.

  11. I read the API documentation and I understand the correct way to do it would be
    void in_received_handler(DictionaryIterator *iter, void *context)
    {
    Tuple *t = dict_read_first(iter);
    if(t)
    {
    //Get string value, if present
    char string_value[32];
    strcpy(string_value, t->value->cstring);
    }

    while (t !=NULL)
    t=dict_read_next(iter);
    if(t)
    {
    //Get string value, if present
    char string_value[32];
    strcpy(string_value, t->value->cstring);
    //text_layer_set_text(outputLayer, t->value->cstring);
    }

    But I still cannot get any data on the pebble App.

    • bonsitm said:

      You are writing the cstring into the string_value buffer and that’s all. Nothing is being stored in the buffers used by the MenuLayer draw cases. Try removing all the commented out code from the sample code that you do not need to make the situation clearer.

  12. Cris,
    In fact, nothing was being stored in the buffers used by the menu Layer.
    And I decided to use something like that but I don’t know how to send the value store in the buffer to the function draw_row_handler

    switch(key) {
    case KEY_OP1:
    //Location received
    snprintf(question_buffer, sizeof(“Q: couldbereallylongname”), “Q: %s”, string_value);
    draw_row_handler(cell_layer, (char*) &op1_buffer)

    • bonsitm said:

      The draw row handler is called asynchronously by the menu layer when a draw is requested. It does not need to be called manually. Be sure to register it when setting up the menu layer!

  13. do your recommend you use a for to iter the dictionary or just as I did with the if(t) and while (t !=NULL)?

    • bonsitm said:

      I use dict read first to assign t, then dict read next while t != NULL to get all data in the received dictionary. That way you are sure to process all available data.

  14. but in which function a apply (char*) &op1_buffer to put the proper content to the buffer?

    • bonsitm said:

      It may not be necessary for the char cast or pointer usage. Experiment!

  15. to register the draw row handler is this .draw_row = (MenuLayerDrawRowCallback) draw_row_handler enough?

    • bonsitm said:

      That’s it precisely! It must be a member of a MenuLayer callbacks set to the MenuLayer.

  16. Cris, I still have problems to show the json string in the Pebble watch application. I have the impression that nothing is stored in the buffers; however, I have fulfilled the conditions to store in the variables buffers.

  17. Actually the first menu is showing the option Unknown, this is a bug!

    • bonsitm said:

      This image shows that there is arow being drawn that is not catered for by a switch case (the default case runs when a certain situation in the switched variable is not met). Make sure all your draw row switch cases that are destined to be drawn have draw calls set up for them. If there are too many, reduce the number of rows in the relevant callback.

  18. I have been checking the code to know why the json information in only shown after I press the up or down button, but I cannot arrive to know why.

    • bonsitm said:

      This could be because your _reload_data() is in the click callback, and not the in_received callback. Check where this call take places.

  19. I’ve changed the handlers and the callback to:
    void up_click_handler(ClickRecognizerRef recognizer, void *context)
    {}

    void select_click_handler(ClickRecognizerRef recognizer, void *context)
    {
    //Create an array of ON-OFF-ON etc durations in milliseconds
    uint32_t segments[] = {100, 200, 500};

    //Create a VibePattern structure with the segments and length of the pattern as fields
    VibePattern pattern = {
    .durations = segments,
    .num_segments = ARRAY_LENGTH(segments),
    };

    //Trigger the custom pattern to be executed
    vibes_enqueue_custom_pattern(pattern);
    }

    void down_click_handler(ClickRecognizerRef recognizer, void *context)
    {}

    void click_config_provider(void *context)
    {
    window_single_click_subscribe(BUTTON_ID_UP, up_click_handler);
    window_single_click_subscribe(BUTTON_ID_DOWN, down_click_handler);
    window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler);
    }

    but I am still getting the string after the up and down button.

  20. Thanks Cris, It works now, I’ve placed the menu_layer_reload_data (menuLayer);

    at the end of void in_received_handler(DictionaryIterator *iter, void *context).

    Can you tell me some suggestion about how to select the option and to updated the json string.

  21. and does the select_single_click_handler() automatically creates a dictionary with the option or do I have to create it?

  22. Cris, I am trying to destroy or unload the window and go back to the mani Pebble menu after the select click is selected and I have tried several methods but it I haven’t had luck.

    • bonsitm said:

      Hi Ant,

      The BACK button will achieve this if you are on the first Window after starting the app, but if you are deeper, try using window_stack_pop() to go back how many Windows deep you are. Eventually you will end up at the main menu when all menus in your app have been unloaded.

  23. Weng Hung said:

    Hi sir, may i ask whether the click_config_provider works in WatchFace??
    I did something like capturing the button pressed in the WatchFace (not in WatchApp) and then performing some actions, but it failed to perform my expected action.
    Thank You.

    • bonsitm said:

      Hi Weng, using clicks is not possible in a watch face due to the buttons being used to open the main menu and scroll the watch face carousel.

      • Weng Hung said:

        Oh >.<..
        so bad..
        I would like to trigger the application on android device quickly, so i would like to set the button in the watchFace.
        It would be late if the i nid to trigger my application from the watchapp in which it requires me to search the menu.
        By the way, is there any option to change the setting of the button on pebble watch? for example, pressing a back button for long, it will trigger my application on android.
        Thank You very much.

      • bonsitm said:

        Hi again, those things you described are unfortunately not possible.

        A possible method of a trigger from a watch face could be using the accelerometer tap function, which can be used from a watch face. Investigate that!

      • Weng Hung said:

        okay. Thank a lot..
        Your tutorials help me a lot.

      • bonsitm said:

        No problem! Best of luck.

  24. First off, awesome tutorials. They are helping me so much.
    I had a question however. I’m back at school (after graduation) getting a comp sci degree and thus far, we have only learned c++, and it’s only been 2 semesters so I apologize if this seems silly.
    In your init() function, I’m not familiar with this syntax:
    WindowHandlers handlers = {
    .load = window_load,
    .unload = window_unload
    };
    So I tried just doing it like:
    WindowHandlers handlers;
    handlers.load = window_load;
    handlers.unload = window_unload;
    and it complies and junk. I’m curious to as why the first syntax? Is it better in any way or just preference? OR is the way I did it simply not right? haha

    • bonsitm said:

      Hi, thanks for the kind words! The first sample you mentioned is a shortcut for the second, similar in nature to initialising an array with literal values, except here you can use knowledge of the object’s members to initialise their values on creation. It’s personal preference, whatever works for you.

Leave a Reply

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

WordPress.com Logo

You are commenting using your WordPress.com 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

%d bloggers like this: