Pebble Watch Face SDK Tutorial #6: 2 Way Communication with Android

Previous Tutorial Sections

All five previous parts of this tutorial series can be found on the ‘Pebble SDK Tutorial’ page link at the top of the page. Please read them if you are not already up to speed!

Introduction

One of the main selling points of the Pebble smartwatch is the fact that it can run apps that communicate with a companion app on an iPhone or Android smartphone. Indeed, it is impossible to get started without one. In the spirit of openness that is also a major selling point, this part of the tutorial series will describe how to accomplish such a task, enabling a much richer watch app experience.

Thanks to the remarkable amount of example code and documentation provided in the relatively early Pebble SDK v1.12, a lot of the code I will be using and showing here is derived from these examples. No need to fix what isn’t broken, and all that! They’ve done an excellent job on this one already.

As I have mentioned previously, I will only be covering the phone app side from an Android perspective, due to the fact I don’t own an iOS device or agree with Apple’s pricing policy to enter the ecosystem, but that’s just personal opinion.

It is assumed that the reader is already familiar with basic Android app development and the application lifecycle. If not, I could write an Android primer, which would in turn require a working knowledge of Java. We’ll see if that is required! Please let me know so I can gauge demand.

AppMessage system overview

On the Pebble platform, this is done using the AppMessage system. On the watch and on the phone, data is stored inside data structures called Dictionaries. When data is send or received, it is stored in data structures called Tuples. A Tuple contains a key and the data itself in what is known as a key-value pair. The key is a ‘label’ for that data and is constant.

For example, if you were writing an app to show the weather, you might have an int to store the temperature. You create a key (number) to identify this data, and all these keys are the same on both the watch app and the phone app. Lets set the key for the temperature variable to a value of ‘5’. The phone sends a Dictionary consisting of a tuple with the temperature data contained within, with a key value of ‘5’. When this data arrives on the watch app, because the key identifying the temperature variable is the same as on the phone app, the Dictionary is searched for the tuple with key value ‘5’, and knows the accompanying data is the temperature to be displayed.

If that explanation leaves you wanting, here’s an diagram that will hopefully make it clearer:

key and data life

Common App Features

In order to make sure that the phone app sends data to the correct watch app, both apps must use the same UUID to identify them. This has already been covered for the watch app, so here is how this is done on the phone app:

UUID member

Here is how the phone app would store the keys. Remember that these should be identical to those declared on the watch app!

KEYS

Watch App Specifics

On the watch app side of things, as predicted there is a use of handler functions to manage the following events:

  • Send successful – AppMessage was sent successfully
  • Send failed – AppMessage failed to send (or was not Acknowledged by phone app)
  • Receive successful – AppMessage received from phone app
  • Receive failed – AppMessage received but dropped due to lack of space or some other error

The first stage to implement this new functionality is to add the following lines to the PebbleAppHandlers in pbl_main():

pbl_main additions

Next, create the handler functions with matching names to those referenced in the ‘.message_info’ callbacks section in pbl_main() for each of the four callback scenarios in the bullet points above. Four blank handlers are shown below:

blank handlers

Sending data to the phone app

To send data to the phone app, a Dictionary is constructed and filled with the tuples describing the data to be sent, using the appropriate keys. Here is an example function that takes an int and sends it to the phone app (adapted from the one provided in the SDK examples):

send_cmd

The argument sent here would be matched with a corresponding action on the phone app. In this example project (link to the source code will be at the end) each of the three buttons is given a key of 0, 1 or 2 (Select, Up, Down). When the up button is pressed,  the watch app sends ‘1’ to the phone app. Because the keys are the same on both sides, when the phone app receives ‘1’ it can use that to perform the action the programmer wants to perform upon an up button press.

Receiving data from the watch on the phone

On the phone app side, the Dictionary is received in a callback method which is registered with the Android system when the app starts, in onResume():

onResume

Hopefully you can see that when the callback is triggered, the data is identified by the key, again it must correspond to the key on the watch app. This value is then used in a switch statement to decide what action to take. The phone app must first acknowledge (ACK) the data so the watch app doesn’t report a timeout.

Another important note is that the callback must be unregistered in onPause() when the phone app is closed to prevent events being missed. Here is how that is done:

onPause

Sending data back to the watch app

Going back in the opposite direction, data is sent to the watch app by making use of a PebbleDictionary and key-value tuples. The methods used for this are provided by the PebbleKit SDK packages, which you can use and import by copying the packages into the ‘src’ folder of your Android project (shown here in Eclipse IDE for Java Developers):

packages

The process for sending data from the phone app to the watch app is largely similar to the same process started from the watch app:

  1. Create the Dictionary (or PebbleDictionary in this case)
  2. Populate it with key-value tuples containing the data to be sent
  3. Send the Dictionary using the UUID (same as the watch app UUID)

This process is summarised in the image below, with comments for clarity:

send to pebble

Receiving data on the watch app

The final part of this process is receiving data from the phone app. This is done by (you guessed it!) extracting data from a Dictionary sent from the phone app and provided by the callback handler as an argument.

  1. A DictionaryIterator is used to search the Dictionary for data using the pre-defined key for that identifies the data received.
  2. This data is stored in a tuple
  3. The tuple contents can then be used as you wish

In the example below, the Dictionary is searched for the string data associated with the DATA_KEY, which was send from the phone app using the same key. This string is then displayed in a TextLayer for the user to see that the data has arrived successfully. Note that the Pebble OS automatically ACKs the received AppMessage:

using received data on watch

Conclusion

So there you have it! Sending data from watch, receiving it on the phone, then sending data from the phone to the watch and using it. I’ll admit, this is the most complicated part of this tutorial series so far, and I hope to provide a bit more of a bridge from API Documentation to understanding the examples.

The example project source code can be found here!

Please feel free to post any questions here on in my Pebble Forum thread, and I’ll do my best to answer. If there is a lot of demand, I may write an Android primer too, but that’s probably another series in itself, and there are plenty of superb ones just a Google search away!

41 comments
  1. prajwal said:

    Hi,
    First of all this would be my first pebble app(second actually , after the ‘Hello World’). I am a beginner in Android as well, but have managed to get some 4-5 Android apps working fine. I tried to get ur app workin on my pebble, but there seems to be few problems. Both the pebble side code and the android side code compiled/build well. I transferred the app to the pebble thru the HTTP server. The app’s name comes up as ‘Template App’ on the pebble. I opened the ‘Template App’ manually on the pebble, but after that nothing seems to work. I hope the first message ‘Ready’ should atleast pop up on the pebble , in my case e1 that isnt popping up .

    The android side app just says ‘No button yet’.Please can you help me !

    Thank you

    • bonsitm said:

      Hi prajwal,

      First off, are you using the source files linked at the end of the post? If you are, you should not be seeing ‘Template App’ but ‘AppMessage’ instead.

      If not, the protocol expected by the watch app must be met by the Android app. Can you post a link to your source files?

  2. prajwal said:

    Yes I am using those source files linked up here , but however I somehow managed to get the app name correctly on my watch and ur watch app is working half way fine. I mean the communication from the android app to the watch app is working. The watch app to the android app isnt working. Infact e1 if there was no communication from the pebble atleast there shud be some text on the pebble showing that the button was pressed right ?
    Even that isnt working. Please help am struggling since may be 12-13hrs. Am right now on it actually 😉

    • bonsitm said:

      From memory that is correct, some output should be shown. From looking at your files I can make the following comments:
      1. Your string sent ‘can u see me’ will not be seen by the watch ever, as onCreate() is called before onResume(), where the data receiver is registered for the first time.
      2. The watch app is likely not working because you haven’t actually registered your click config provider. See the docs here: . This should be done in handle_init().

      Try these and report back!

  3. prajwal said:

    woooooooowwwwwwwww 12-13hrs of sittin perplexed just for a line of code. Its working magically. I mean everything as it should. Thanx a tonne for the help.

    P.S: The message ‘Can u c me’ is actually seen on my pebble. Please chk the dropbox link above, I have uploaded a snapshot of it. Also please let me know how it is displaying the message as you said it wudn.

    • bonsitm said:

      Great to hear it’s working!

      I was actually wrong before, as the message ‘can u c me’ will be delivered to the watch app regardless of whether an Android listener is in place.

  4. Johnny said:

    Hi there, I tweet you for your email. I want to use only the up and down button. But in the sdk 2 it does not work. Can you help me please.

    Thank you

    • bonsitm said:

      Hi, I did DM you. I will be getting to AppMessage for SDK 2.0 in due course. What part does not work? Try the guide at the dev site in the mean time.

      • Johnny said:

        Thanks 🙂 I struggle with the “send key’s”

        #include

        Window* window;
        TextLayer *text_layer;

        //Set of enumerated keys with names and values. Identical to Android app keys
        enum {
        SELECT_KEY = 0x0, // TUPLE_INTEGER
        UP_KEY = 0x01,
        DOWN_KEY = 0x02,
        DATA_KEY = 0x0
        };

        /**
        * Handler for up click
        */
        void up_single_click_handler(ClickRecognizerRef recognizer, Window *window) {
        //Send the UP_KEY
        send_cmd(UP_KEY);

        //Show it was sent on the watch app
        text_layer_set_text(&textLayer, “FlashLight on!”);
        }

        /**
        * Handler for down click
        */
        void down_single_click_handler(ClickRecognizerRef recognizer, Window *window) {
        //Send the DOWN_KEY
        send_cmd(DOWN_KEY);

        //Show it was sent on the watch app
        text_layer_set_text(&textLayer, “FlashLight off!”);
        }

        void window_load(Window *window)
        {
        //We will add the creation of the Window’s elements here soon!
        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, “Pebble FlashLight for Android!”);
        }

        void window_unload(Window *window)
        {
        //We will safely destroy the Window’s elements here!
        text_layer_destroy(text_layer);
        }

        void init()
        {
        //Initialize the app elements here!
        window = window_create();
        window_set_window_handlers(window, (WindowHandlers) {
        .load = window_load,
        .unload = window_unload,
        });

        window_stack_push(window, true);
        }

        void deinit()
        {
        //De-initialize elements here to save memory!
        window_destroy(window);
        }

        int main(void)
        {
        init();
        app_event_loop();
        deinit();
        }

      • bonsitm said:

        Hi Johnny, the send_cmd() function is not a feature of the Pebble SDK, but a wrapper derived by me. It is detailed in 1.X terms in the Tutorial section you mentioned, and accepts two arguments; the key and a value.

        Have another look! You will need to include the full definition of send_cmd() in your C file and possibly make modifications for SDK 2.0. Or wait until I reach this stage in the 2.0 Tutorial series.

  5. Johnny said:

    I can send you my files?

    ../src/main.c: In function ‘up_single_click_handler’:
    ../src/main.c:20:3: error: implicit declaration of function ‘send_cmd’ [-Werror=implicit-function-declaration]
    ../src/main.c:23:24: error: ‘textLayer’ undeclared (first use in this function)
    ../src/main.c:23:24: note: each undeclared identifier is reported only once for each function it appears in
    ../src/main.c: In function ‘down_single_click_handler’:
    ../src/main.c:34:24: error: ‘textLayer’ undeclared (first use in this function)
    cc1: all warnings being treated as errors

  6. Johnny said:

    Thank you! And thank you for the blog this really help!!

    • bonsitm said:

      Thank you. Keep persevering!

  7. Weng Hung said:

    hi sir, do you have the sample for 2 way communication with Android in version 2.x??
    This sample can not be run in my cloud pebble because it is version 1.x..
    Thank You.

  8. Weng Hung said:

    is my coding below correct (version 2.x)
    #include
    ///f1db3db2-0b0e-47c1-86af-e28d5ac7767c
    #define UUID { 0xf1, 0xdb, 0x3d, 0xb2, 0x0b, 0x0e, 0×47, 0xc1, 0×86, 0xaf, 0xe2, 0x8d, 0x5a, 0xc7, 0×76, 0x7c }
    Window* window;
    TextLayer *text_layer;
    InverterLayer *inv_layer;
    char buffer[] = “00:00″;
    enum {
    STATUS_KEY = 0,
    MESSAGE_KEY = 1
    };
    void send_message(void){
    DictionaryIterator *iter;
    app_message_outbox_begin(&iter);
    dict_write_uint8(iter, STATUS_KEY, 0×1);
    dict_write_cstring(iter, MESSAGE_KEY, “Hi Phone, I’m a Pebble!”);
    dict_write_end(iter);
    app_message_outbox_send();
    }
    void out_sent_handler(DictionaryIterator *sent, void *context) {
    text_layer_set_text(text_layer, “Send successful!”);
    }
    static void out_failed_handler(DictionaryIterator *failed, AppMessageResult reason, void* context) {
    text_layer_set_text(text_layer, “Send Failed”);
    }
    void back_click_handler(ClickRecognizerRef recognize, void *context)
    {
    vibes_long_pulse();
    send_message();
    /*DictionaryIterator *iter;
    app_message_outbox_begin(&iter);
    Tuplet value = TupletInteger(1, 42);
    dict_write_tuplet(iter, &value);
    app_message_outbox_send();*/
    }
    void click_config_provider(void *context)
    {
    window_multi_click_subscribe(BUTTON_ID_BACK, 3, 10, 1000, true, back_click_handler);
    }
    void tick_handler(struct tm *tick_time, TimeUnits units_changed)
    {
    //Format the buffer string using tick_time as the time source
    strftime(buffer, sizeof(“00:00″), “%H:%M”, tick_time);
    //Change the TextLayer text to show the new time!
    text_layer_set_text(text_layer, buffer);
    }
    void window_load(Window *window)
    {
    //Time layer
    text_layer = text_layer_create(GRect(0, 48, 132, 168));
    text_layer_set_background_color(text_layer, GColorClear);
    text_layer_set_text_color(text_layer, GColorBlack);
    text_layer_set_text_alignment(text_layer, GTextAlignmentCenter);
    text_layer_set_font(text_layer, fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD));
    layer_add_child(window_get_root_layer(window), (Layer*) text_layer);
    //Inverter layer
    inv_layer = inverter_layer_create(GRect(0, 45, 144, 62));
    layer_add_child(window_get_root_layer(window), (Layer*) inv_layer);
    //Get a time structure so that the face doesn’t start blank
    struct tm *t;
    time_t temp;
    temp = time(NULL);
    t = localtime(&temp);
    //Manually call the tick handler when the window is loading
    tick_handler(t, MINUTE_UNIT);
    }
    void window_unload(Window *window)
    {
    //We will safely destroy the Window’s elements here!
    text_layer_destroy(text_layer);
    inverter_layer_destroy(inv_layer);
    }
    void init()
    {
    //Initialize the app elements here!
    window = window_create();
    window_set_window_handlers(window, (WindowHandlers) {
    .load = window_load,
    .unload = window_unload,
    });
    app_message_register_outbox_sent(out_sent_handler);
    app_message_register_outbox_failed(out_failed_handler);
    window_set_click_config_provider(window, click_config_provider);
    tick_timer_service_subscribe(MINUTE_UNIT, (TickHandler) tick_handler);
    window_stack_push(window, true);
    app_message_open(512, 512);
    }
    void deinit()
    {
    tick_timer_service_unsubscribe();
    window_destroy(window);
    }
    int main(void)
    {
    init();
    app_event_loop();
    deinit();
    }

    • bonsitm said:

      Looks okay on the Pebble side. Why are you using the BACK button? It is used by the system for exiting the app.

      • Weng Hung said:

        hmm… yup, but it’s okay..
        it can be changed to any others button.
        So, you mean my pebble side is okay, so do I need the .js file?
        Is .js file necessary in my case?
        I just want to trigger my mobile application from the pebble watch application by pressing the back button for several times.
        Thank You.

      • bonsitm said:

        The mobile application should implement a PebbleDataReceiver’s onReceive method to receive the messages. You should not need a JS file.

  9. Weng Hung said:

    By the way, I downloaded the whole project you gave here.
    And I changed the pebble coding into version 2 and I copy all the codes from the Android file into the Eclipse, and install into my phone.
    However, it seems like no communication…..
    How to solve the mobile application part?
    Thank You.

    • bonsitm said:

      Do the UUIDs match on both sides? This is essential.

  10. Weng Hung said:

    ya, all the code are same as the one you given.
    as shown below
    package com.example.fyp;
    import java.util.UUID;
    import android.app.Activity;
    import android.content.Context;
    import android.os.Bundle;
    import android.util.Log;
    import android.widget.TextView;
    import com.getpebble.android.kit.PebbleKit;
    import com.getpebble.android.kit.util.PebbleDictionary;
    public class MainActivity extends Activity {
    //Keys – identical to watch app keys
    private static final int DATA_KEY = 0;
    private static final int SELECT_BUTTON_KEY = 0;
    private static final int UP_BUTTON_KEY = 1;
    private static final int DOWN_BUTTON_KEY = 2;
    private static final int BUFFER_LENGTH = 32;
    //Use the same UUID as on the watch
    private static final UUID APP_UUID = UUID.fromString(“f1db3db2-0b0e-47c1-86af-e28d5ac7767c”);
    private PebbleKit.PebbleDataReceiver dataHandler;
    private TextView statusLabel;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    //Allocate the TextView used to show which button press was received
    //statusLabel = (TextView) findViewById(R.id.buttonLabel);
    }
    @Override
    public void onResume() {
    super.onResume();
    //Start the companion app on the watch
    PebbleKit.startAppOnPebble(getApplicationContext(), APP_UUID);
    //Create a DataReciever
    dataHandler = new PebbleKit.PebbleDataReceiver(APP_UUID) {
    @Override
    public void receiveData(final Context context, final int transactionId, final PebbleDictionary data) {
    //ACK to prevent timeouts
    PebbleKit.sendAckToPebble(context, transactionId);
    //Get which button was pressed
    int buttonPressed = data.getUnsignedInteger(DATA_KEY).intValue();
    //Update UI
    switch(buttonPressed) {
    case SELECT_BUTTON_KEY: {
    statusLabel.setText(“Select button pressed”);
    sendStringToPebble(“Phone says ‘Select pressed!’”);
    break;
    }
    case UP_BUTTON_KEY: {
    statusLabel.setText(“Up button pressed”);
    sendStringToPebble(“Phone says ‘Up pressed!’”);
    break;
    }
    case DOWN_BUTTON_KEY: {
    statusLabel.setText(“Down button pressed”);
    sendStringToPebble(“Phone says ‘Down pressed!’”);
    break;
    }
    default: {
    statusLabel.setText(“Unknown button!”);
    break;
    }
    }
    }
    };
    //Register the DataHandler with Android to receive any messages from the watch
    PebbleKit.registerReceivedDataHandler(getApplicationContext(), dataHandler);
    }
    @Override
    public void onPause() {
    super.onPause();
    // Always deregister any Activity-scoped BroadcastReceivers when the Activity is paused
    if (dataHandler != null) {
    unregisterReceiver(dataHandler);
    dataHandler = null;
    }
    }
    /**
    * Send a string to the companion Pebble Watch App
    * @param message The string to send
    */
    private void sendStringToPebble(String message) {
    if(message.length() < BUFFER_LENGTH) {
    //Create a PebbleDictionary
    PebbleDictionary dictionary = new PebbleDictionary();
    //Store a string in the dictionary using the correct key
    dictionary.addString(DATA_KEY, message);
    //Send the Dictionary
    PebbleKit.sendDataToPebble(getApplicationContext(), APP_UUID, dictionary);
    } else {
    Log.i("sendStringToPebble()", "String too long!");
    }
    }
    }

    • bonsitm said:

      Ensure the UUID in appinfo.json matches the one in Eclipse.

      • Weng Hung said:

        sir, I do not have a .json file in my cloud pebble.
        May i know how to create one?

      • bonsitm said:

        Ah. CloudPebble manages this for you. You can find the UUID you must use in Eclipse under Settings then App UUID

  11. Weng Hung said:

    I do not get it sir.
    Besides pasting all the code you given into the Eclipse, is there any steps to do?

    • bonsitm said:

      Change the UUID given in Eclipse where applicable to the one given in CloudPebble Settings

      • Weng Hung said:

        Okay sir, I have already change the UUID in Eclipse which same as the UUID given by the cloudPebble.

      • bonsitm said:

        Does it now communicate?

      • Weng Hung said:

        Sir, I have changed both the UUID in the Eclipse and pebble given by the clouldPebble, but the mobile application still can not be triggered.
        Do I need to install the mobile application into the pebble application or just install it into the phone?

      • bonsitm said:

        Both apps must be installed onto their respective devices and the Android app must already be launched when the pebble button is pressed.

      • Weng Hung said:

        Yes, I already install the watch app into my pebble watch and I install the mobile application into my smartphone. However, my mobile application is not linked to the pebble mobile application.
        So, how to link my mobile application to the pebble mobile application?
        Thank You.

      • bonsitm said:

        The two apps are linked by having the same UUID. When the watch app sends a message, the phone app uses the UUID as a filter to receive messages from only that one watchapp

      • Weng Hung said:

        It still can not be run, sir.
        Do you have a simple code for Android and Pebble that (the pebble app will start the mobile application when the back button is pressed for several times whereby it does not need to send any message content.)
        For the mobile application part, it does not need to receive any message content sent from pebble watch whereby it only have to start the application when the button is pressed on the pebbleWatch.
        I do not know why my code cannot be run, could sir help me by giving the simple code to me?
        Thank You.

      • bonsitm said:

        Ah I understand what you want now. Launching an Android app is a more complex task than simply sending a message. Look into Android BroadcastReceivers and Services. An understanding of these is required to achieve this task.

  12. Weng Hung said:

    Sorry sir, because i am new to Pebble and Android.
    And I will have to submit the assignment of the Pebble (pressing button to trigger the mobile application) in 1 week time.
    ><

    • bonsitm said:

      You have my sympathies! I am also closing on deadlines…

  13. Anil Eyupoglu said:

    When i try the code (from the example project you put in the conclusion part), it gives me errors saying ‘pebble_os.h’ couldn’t find. When i delete them and try just to include classic ‘pebble.h’, it gives errors again saying PebbleAppHandlers etc. are not defined. I think it’s something to do with the versions. I am so confused. I would be extremely appreciated if someone help me. Thank you so much!

    • bonsitm said:

      Make sure the first line of your C file is ‘#include ‘ and that you have a valid Pebble SDK installed

Leave a reply to bonsitm Cancel reply