Editor's Note: Pebble was purchased by Fitbit in 2016. The links in the article for the blog and for the SDK no longer go to valid pages.

Pebble is a “smart” watch developed by Pebble Technology Corporation. It's one of the most successful Kickstarter projects to-date and has received significant success with consumers. The Pebble watch comes with a black-and-white e-paper display, and includes several sensors such as a magnetometer, an ambient light sensor, and an accelerometer. The Pebble watch supports the following types of applications:

  • Standalone apps that run on the Pebble
  • Standalone apps that run on the Pebble and can communicate with either an iPhone or Android app
  • Standalone apps that run on the Pebble and can communicate with a Javascript application running on a mobile device
  • Sports apps that run on iPhone or Android devices and send the data directly to the Pebble watch

In this article, I'll walk you through the process of creating a Pebble application and how you can extend its functionality by writing JavaScript code to connect it to the outside world.

Pebble Specifications

The Pebble comes in two varieties: a classic “plastic” Pebble ($150) and a classier Pebble Steel ($249) (see Figure 1). Other than the look (and a bit more memory), the Pebble Steel is identical to the classic Pebble.

NOTE: Images of the Pebble watch are from the Pebble website:

Figure 1: The Pebble and the Pebble Steel
Figure 1: The Pebble and the Pebble Steel

The primary way of interacting with the Pebble is through its four buttons; there's one on the left (the Back button), and three on the right (Up, Select, and Down). You can see this in Figure 2.

Figure 2: The four buttons on the Pebble
Figure 2: The four buttons on the Pebble

In terms of hardware specs, the Pebble has the following:

  • A 1.26" display containing 144 x 168 pixels
  • Black-and-white e-paper display
  • 4MB flash memory for the classic Pebble and 8MB flash memory for the Pebble Steel
  • An ARM Cortex-M3 processor running at maximum 120Mhz
  • A 140mAh battery that can last five to seven days of normal usage on a single charge

In terms of connectivity, the Pebble supports only Bluetooth, albeit two types of Bluetooth, both Classic and Low Energy (LE). When you pair up the Pebble with your mobile device, there will be two types of Bluetooth connections if your mobile device supports Bluetooth LE. The Classic Bluetooth connection is used by mobile device to install apps on your Pebble, as well as for delivering caller IDs, remote music control, updates, etc. The Bluetooth LE connection, on the other hand, supports iOS 7's Apple Notification Center Service (ANCS) for sending notifications over Bluetooth LE.

Connecting the Pebble to the Mobile Device

As the Pebble has no network connectivity options apart from its Bluetooth connections, it must rely on your mobile device to communicate with the outside world. It does so through the Pebble Mobile app. At this time, the Pebble Mobile app is available on the iOS and Android platforms.

The role of the Pebble Mobile app is to:

  • Install applications (the binary of a Pebble app has the .pbw extension) onto the Pebble
  • Provide the Pebble App Store and allow users to browse and install apps (see Figure 3) directly on the Pebble
  • Load and unload apps from the Pebble onto the App Locker (there is a maximum of eight apps that can be installed on the Pebble at any one time) (see Figure 4)
  • Provide a development platform where developers can deploy applications onto the Pebble
Figure 3: The Pebble App Store provides the central repository for Pebble apps.
Figure 3: The Pebble App Store provides the central repository for Pebble apps.
Figure 4: The Pebble Mobile app manages the application installed on the Pebble.
Figure 4: The Pebble Mobile app manages the application installed on the Pebble.

Figure 5 shows the role played by the Pebble Mobile app.

Figure 5: The roles played by the Pebble Mobile App
Figure 5: The roles played by the Pebble Mobile App

This last point is of interest to developers. For deploying Pebble apps, the Pebble Mobile app acts as a server and the developer deploys the Pebble app through it. Both the mobile device (iOS or Android devices hosting the Pebble Mobile app) and the development computer must be on the same WiFi network.

Preparing Your Mobile Device

For this article, use an iPhone to host the Pebble Mobile app. Once the Pebble Mobile app is installed and paired with the Pebble, go to the Settings page, tap on the Pebble app, and turn on Developer Mode (see Figure 6).

Figure 6: Turning on the Pebble Developer Mode
Figure 6: Turning on the Pebble Developer Mode

Once the Developer Mode is turned on, you will be able to see the DEVELOPER option (left Figure 7; turned off by default). Tap the option and enable it (right Figure 7).

Figure 7: The IP address of the Pebble Mobile app
Figure 7: The IP address of the Pebble Mobile app

Once the Developer Mode is enabled, the Pebble Mobile app acts as the server for deploying the application onto your Pebble. Observe the IP address of the server. This is the IP address of your Pebble mobile app as well as your iPhone.

Developing for Pebble

There are two ways to develop for Pebble:

For this article, I used the SDK provided by Pebble.

Installing the SDK

The easiest way to install the Pebble SDK is to use a Mac and enter the following command in the Terminal window:

curl -sSL
install.sh | sh && source ~/.bash_profile

If everything installs correctly, you should see a pebble-dev folder in your home directory. If not, check out the installation help provided by Pebble at: https://developer.getpebble.com/2/getting-started/.

To test the Pebble SDK, issue the following commands:

MyMac:~ wei-menglee$ cd pebble-dev
MyMac:pebble-dev wei-menglee$ pebble new-project
HelloWorld
Creating new project HelloWorld

The above commands change the working directory to the pebble-dev directory and create a new Pebble project named HelloWorld. Once the project is created, you'll be able to see a folder named HelloWorld. Within this folder, you'll be able to see the files shown in Figure 8.

Figure 8: The folder containing the HelloWorld Pebble project
Figure 8: The folder containing the HelloWorld Pebble project

The use of the various files and folders are:

  • appinfo.json: contains the application's information, such as application name, icons, etc.
  • resources: a folder containing the applications resources, such as images, etc.
  • src: a folder containing the application's source code
  • HelloWorld.c: the main Pebble source code
  • Wscript: information for the compiler compiling the Pebble app

To build the project, change the directory to the folder containing the project use the pebble build command, like this:

MyMac:pebble-dev wei-menglee$ cd HelloWorld/
MyMac:HelloWorld wei-menglee$ pebble build

If the compilation is successful (you will know if it isn't), you'll find a new folder called build within the project. Within the build folder is a file with the .pbw extension. This is the deployable Pebble application that you can send through email to someone with a Pebble watch to install through the Pebble Mobile app. For now, let's learn how to deploy this application directly onto your Pebble watch. Issue the following command:

MyMac:HelloWorld wei-menglee$ pebble install --logs --phone <ip_address_of_your_mobile_phone>
[INFO    ] Enabling application logging...
[INFO    ] Installation successful
[INFO    ] I app_manager.c:138 Heap Usage for <Timely>:
Total Size <6364B> Used <5524B> Still allocated
<112B>
[INFO    ] D HelloWorld.c:89 Done initializing, pushed
window: 0x2001a5d8
[INFO    ] Displaying logs ... Ctrl-C to interrupt.

The Pebble application is now deployed to your Pebble watch through the Pebble Mobile app.

When deploying an application onto your Pebble, ensure that the Pebble Mobile app on your mobile device is running in the foreground.

Figure 9 shows the application running on the Pebble watch. Press any of the three buttons on the right and observe the message printed on the screen.

Figure 9: The HelloWorld application on the Pebble
Figure 9: The HelloWorld application on the Pebble

Examining the HelloWorld Example

Now that you've run the HelloWorld application on your Pebble, let's take a look at how it works. Listing 1 shows the content of the HelloWorld.c file. Instead of explaining the code line-by-line, I've added comments throughout the code to explain how it works.

Listing 1: The HelloWorld.c file

#include <pebble.h>

//---pointers to the Window and TextLayer---
static Window *window;
static TextLayer *text_layer;

//---the click event handler for the SELECT button---
static void select_click_handler(
    ClickRecognizerRef recognizer, void *context) {
        text_layer_set_text(text_layer, "Select");
    }

//---the click event handler for the UP button---
static void up_click_handler(
    ClickRecognizerRef recognizer, void *context) {
        text_layer_set_text(text_layer, "Up");
    }

//---the click event handler for the DOWN button---
static void down_click_handler(
    ClickRecognizerRef recognizer, void *context) {
        text_layer_set_text(text_layer, "Down");
    }

//---the click configuration provider function---
static void click_config_provider(void *context) {
    //---subscribe to the click events of the three buttons on the
    // Pebble---
    window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler);
    window_single_click_subscribe(BUTTON_ID_UP, up_click_handler);
    window_single_click_subscribe(BUTTON_ID_DOWN, down_click_handler);
}

//---called when the window gets loaded---
static void window_load(Window *window) {
    //---gets the root layer of the window---
    Layer *window_layer = window_get_root_layer(window);

    //---gets the bound of the layer---
    GRect bounds = layer_get_bounds(window_layer);

    //---creates a new text TextLayer on the heap and init with
    // default values---
    text_layer = text_layer_create((GRect) {
        .origin = { 0, 72 },
        .size = { bounds.size.w, 20 } });

    //---sets the text to display in the TextLayer---
    text_layer_set_text(text_layer, "Press a button");

    //---sets the alignment of the TextLayer---
    text_layer_set_text_alignment(text_layer, GTextAlignmentCenter);

    //---adds the TextLayer onto the Window layer---
    layer_add_child(window_layer, text_layer_get_layer(text_layer));
}

//---called when the window is unloaded---
static void window_unload(Window *window) {
    //---destroys the TextLayer---
    text_layer_destroy(text_layer);
}

static void init(void) {
    //---creates a new Window on the heap and initialize with the
    // default values---
    window = window_create();

    //---sets the click configuration provider callback function on
    // the window---
    window_set_click_config_provider(window, click_config_provider);

    //---sets the window handlers of the Window---
    window_set_window_handlers(window, (WindowHandlers) {
        //---the function to call when the window is loaded---
        .load = window_load,
        //---the function to call when the window is unloaded---
        .unload = window_unload,
    });

    //---push the window onto the navigation stack using sliding
    // animation---
    const bool animated = true;
    window_stack_push(window, animated);
}

static void deinit(void) {
    //---destroys the window---
    window_destroy(window);
}

//---the starting point for your app---
int main(void) {
    //---call this when the app is started---
    init();

    //---a macro to call the app_log function to display debug info---
    APP_LOG(APP_LOG_LEVEL_DEBUG,
    "Done initializing, pushed window: %p", window);

    //---enters the app event loop; blocking call---
    app_event_loop();

    //---call this when the app gets killed---
    deinit();
}

Here are some of the important points:

  • Pebble uses the C programming language. There are no classes and objects in C, and in Pebble programming, you deal mainly with value types, such as int, float, char, and structures.
  • You often need to deal with pointers. A pointer is a variable that contains the address of another variable and “points” to that variable in memory. A pointer has the following syntax: variable_type*pointer_name;
  • A Pebble app contains one or more Windows. A Window contains the UI of your application. Windows are added to a stack, and only the topmost window is visible at any time.
  • You add UI elements to a Window's layer. The various layers of a Window make up the UI of your Pebble application. One example of such a layer is the TextLayer, which allows you to display text.
  • The Pebble SDK comes with a number of functions that lets you create new structures. An example would be the text_layer_create() that creates and returns a pointer to a TextLayer structure. The best way to explore these functions is to check out the documentation using the Pebble documentation located at .
  • You need to perform your own memory management. In general, if you create something using functions that end with the _create name, such as text_layer_create() and window_create(), you need to free up the memory using a corresponding _destroy function, such as text_layer_destroy() and window_destroy(). Failure to call the corresponding destroy function results in a memory leak.

To exit from the Pebble application that's currently running on your Pebble, press the Back button. In the Terminal window, you should be able to see the output from the Pebble:

[INFO] I app_manager.c:138 Heap Usage for <HelloWorld>: Total Size <23084B> Used <204B> Still allocated <0B>

In particular, look for the part where it says: Still allocated <0B>. If you see a number other than zero, it means that there is a memory leak.

Creating Your Own Custom Watch

With the obligatory HelloWorld application out of the way, let's now create something useful from the APIs provided by the Pebble SDK. For this exercise, you'll create your own clock that displays the current time. First, issue the following command to create a new project named MyWatch:

MyMac:pebble-dev wei-menglee$ pebble new-project
MyWatch

Displaying the Current Time

The next thing to do is to display the current time of the Pebble app. Edit the MyWatch.c file located in the src folder of the project and add in the code highlighted by comments shown in Listing 2.

Listing 2: Displaying the current time

#include <pebble.h>

static Window *window;
static TextLayer *text_layer;

//---add in the following statements---
static TextLayer *text_layer_second;

char str_hour[3];     //---e.g. "15\0"---
char str_minute[3];   //---e.g. "59\0"---
char str_second[3];   //---e.g. "30\0"---
char str_current_hourmin[6];  //---store hr:min; e.g. 15:59\0---

static void handle_second_tick(
    struct tm *tick_time, TimeUnits units_changed) {
        int hour = tick_time->tm_hour;   //---get the hour---
        int minute = tick_time->tm_min;  //---get the minute---
        int second = tick_time->tm_sec;  //---get the second---

        //---convert to string---
        snprintf(str_hour, sizeof(str_hour), "%02d", hour);
        snprintf(str_minute, sizeof(str_minute), "%02d", minute);
        snprintf(str_second, sizeof(str_second), "%02d", second);

        //---form the string to display the current time---
        strcpy(str_current_hourmin, str_hour);
        strcat(str_current_hourmin, ":");
        strcat(str_current_hourmin, str_minute);

        //---display the current hour:minute in a TextLayer---
        text_layer_set_text(text_layer, str_current_hourmin);

        //---display the current second in another TextLayer---
        text_layer_set_text(text_layer_second, str_second);
    }
    //---end of statements to add---

    static void select_click_handler(
        ClickRecognizerRef recognizer, void *context) {
            text_layer_set_text(text_layer, "Select");
        }

        static void up_click_handler(
            ClickRecognizerRef recognizer, void *context) {
                text_layer_set_text(text_layer, "Up");
            }

        static void down_click_handler(
            ClickRecognizerRef recognizer, void *context) {
                text_layer_set_text(text_layer, "Down");
            }

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

        static void window_load(Window *window) {
            Layer *window_layer = window_get_root_layer(window);
            GRect bounds = layer_get_bounds(window_layer);

            //---add in the following statements---
            //---modify the text layer size to display larger text---
            text_layer = text_layer_create((GRect) {
                .origin = { 0, 42 },
                .size = { bounds.size.w, 42 } });
            //---end of statements to add---

            text_layer_set_text(text_layer, "Press a button");
            text_layer_set_text_alignment(text_layer, GTextAlignmentCenter);

            //---add in the following statements---
            //---display using a larger font size---
            text_layer_set_font(text_layer,
               fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD));

             //---create a second text layer to display the second---
             text_layer_second = text_layer_create((GRect) {
                 .origin = { 0, 90 },
                 .size = { bounds.size.w, 28 }
              });

              //---set the alignment and font size---
              text_layer_set_text_alignment(text_layer_second, GTextAlignmentCenter);

              text_layer_set_font(text_layer_second,
              fonts_get_system_font(FONT_KEY_GOTHIC_28));
               //---end of statements to add---

               layer_add_child(window_layer,
                   text_layer_get_layer(text_layer));

                //---add in the following statements---
                //---add the second text layer to the window layer---
                layer_add_child(window_layer,
                    text_layer_get_layer(text_layer_second));
                //---end of statements to add---
        }

        static void window_unload(Window *window) {
            text_layer_destroy(text_layer);

            //---add in the following statements---
            //---destroy the text layer---
            text_layer_destroy(text_layer_second);
            //---end of statements to add---
        }

        static void init(void) {
            window = window_create();
            window_set_click_config_provider(
                window, click_config_provider);
            window_set_window_handlers(window, (WindowHandlers) {
                .load = window_load,
                .unload = window_unload,
             });

             //---add in the following statements---
             //---subscribe to the tick timer service---
             tick_timer_service_subscribe(SECOND_UNIT, handle_second_tick);
             //---end of statements to add---

             const bool animated = true;
             window_stack_push(window, animated);
        }

        static void deinit(void) {
            //---add in the following statements---
            //---unsubscribe from the tick timer service---
            tick_timer_service_unsubscribe();
            //---end of statements to add---

            window_destroy(window);
        }

        int main(void) {
            init();

            APP_LOG(APP_LOG_LEVEL_DEBUG,
              "Done initializing, pushed window: %p", window);

            app_event_loop();
            deinit();
        }

Here is what the application does:

  • It uses the Tick Timer Service to call your app every time a component unit (such as second, minute, hour, day, month, and year) changes.
  • It subscribes to a tick timer event service using the tick_timer_service_subscribe() function and passes it the time component unit,and the event handler.
  • It unsubscribes from a tick timer event service, using the tick_timer_service_unsubscribe() function.
  • The handle_second_tick function fires every second and the current time is passed in via a tm structure.
  • In order to print out the current time, it needs the components (hour, minute, second) converted into their string equivalents. You can do this using the snprintf() function.

Deploy the application to the Pebble by building it and then installing it. You should then see the application displaying the current time (see Figure 10) and updating every second.

Figure 10: The application displays the current time.
Figure 10: The application displays the current time.

Inverting the Screen

Sometimes the user might prefer the application to have a black background (with white text). You can easily achieve this by using the InverterLayer layer. Add the comment-highlighted code to the MyWatch.c file as shown in Listing 3.

Listing 3: Using the InverterLayer to invert the screen

#include <pebble.h>

static Window *window;
static TextLayer *text_layer;
static TextLayer *text_layer_second;

//---add in the following statements---
static InverterLayer *inverter_layer;
//---end of statements to add---

char str_hour[3];       //---e.g. "15\0"---
char str_minute[3];     //---e.g. "59\0"---
char str_second[3];     //---e.g. "30\0"---
char str_current_hourmin[6];  //---store hr:min; e.g. 15:59\0---

static void handle_second_tick(
...
}

static void select_click_handler(
    ClickRecognizerRef recognizer, void *context) {
        text_layer_set_text(text_layer, "Select");
    }

    static void up_click_handler(
        ClickRecognizerRef recognizer, void *context) {

        //---add in the following statements---
        //---change the text to display Black when the UP button is
        // pressed---
        text_layer_set_text(text_layer, "Black");

        //---add the inverter layer to the window layer---
        Layer *window_layer = window_get_root_layer(window);
        layer_add_child(window_layer, 
          inverter_layer_get_layer(inverter_layer));
         //---end of statements to add---
        }

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

            //---add in the following statements---
            //---change the text to display White when the DOWN 
            // button is pressed---

            text_layer_set_text(text_layer, "White");

            //---remove the inverter layer from the window layer---
            layer_remove_from_parent(
              inverter_layer_get_layer(inverter_layer));
            //---end of statements to add---
    }

    ...

    static void window_load(Window *window) {
        Layer *window_layer = window_get_root_layer(window);
        GRect bounds = layer_get_bounds(window_layer);

        text_layer = text_layer_create((GRect) {
            .origin = { 0, 42 },
            .size = { bounds.size.w, 42 } });

        text_layer_set_text(text_layer, "Press a button");
        text_layer_set_text_alignment(
          text_layer, GTextAlignmentCenter);

        text_layer_set_font(text_layer,
          fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD));

        text_layer_second = text_layer_create((GRect) {
            .origin = { 0, 90 },
            .size = { bounds.size.w, 28 }
        });
        text_layer_set_text_alignment(
            text_layer_second, GTextAlignmentCenter);
        text_layer_set_font(text_layer_second,
          fonts_get_system_font(FONT_KEY_GOTHIC_28));

        layer_add_child(window_layer, text_layer_get_layer(text_layer));

        layer_add_child(window_layer, text_layer_get_layer(text_layer_second));

        //---add in the following statements---
        //---create an inverter layer covering the entire window---
        inverter_layer = inverter_layer_create(
          GRect(0, 0, bounds.size.w, bounds.size.h));
        //---end of statements to add---
    }

    static void window_unload(Window *window) {
        text_layer_destroy(text_layer);
        text_layer_destroy(text_layer_second);

       //---add in the following statements---
       //---destroy the inverter layer---
       inverter_layer_destroy(inverter_layer);
       //---end of statements to add---
}

The InverterLayer inverts the graphics context for all the layers that are “behind” it; it changes all the black pixels to white and vice versa. The InverterLayer is useful for cases where you need to highlight the selection of an item in a menu. To switch back to the original white background, you remove the InverterLayer. Figure 11 shows how the application looks when the background of the application is inverted (when you press the Up button on the Pebble).

Figure 11: The application with the screen inverted
Figure 11: The application with the screen inverted

Displaying the Application Menu Icon

Next, modify the application to display an icon on the application menu. The application menu is where users navigate through the list of applications available on the Pebble (see Figure 12).

Figure 12: The Pebble's Application Menu
Figure 12: The Pebble's Application Menu

As the Pebble's screen is only black and white, you need to prepare an image that is also black-and-white. Also, for the application menu icon, the image size must be 28x28 pixels.

The Pebble SDK automatically converts color or grayscale images into black and white bitmaps. I recommend that you convert it yourself using your own image-processing software, such as HyperDither.

Once you've prepared the image, create a folder named images within the resources folder of the project and drag and drop the image into it (see Figure 13).

Figure       13: The icon in the images folder within the resources folder
Figure 13: The icon in the images folder within the resources folder

You also need to modify the appinfo.json file to inform the compiler that you are setting an application menu icon. Add the code shown after the media attribute in the following snippet:

{
    "uuid": "ad7b91ec-3b37-4e14-950d-2ebff36295fe",
    "shortName": "MyWatch",
    "longName": "MyWatch",
    "companyName": "MakeAwesomeHappen",
    "versionCode": 1,
    "versionLabel": "1.0.0",
    "watchapp": {
        "watchface": false
    },
    "appKeys": {
        "dummy": 0
    },
    "resources": {
        "media": [
            {
                "type": "png",
                "menuIcon": true,
                "name": "IMAGE_MENU_ICON",
                "file": "images/icon.png"
            }
        ]
    }
}

That's it! Build the application and then deploy it onto the Pebble. When you go to the application menu now, you'll see the icon displayed next to your application name (see Figure 14).

Figure       14      : The application menu icon displayed next to the application name
Figure 14 : The application menu icon displayed next to the application name

Drawings

Besides displaying images, Pebble also supports the drawing of common shapes, such as circles, rectangles, paths, lines, etc. Let's now add the highlighted code to the MyWatch.c file shown in Listing 4.

Listing 4:. Drawing rectangles in Pebble to represent a battery

#include <pebble.h>

static Window *window;
static TextLayer *text_layer;
static TextLayer *text_layer_second;

static InverterLayer *inverter_layer;

//---add in the following statements---
//---the layer for drawing---
static Layer *mydrawings_layer;
//---end of statements to add---

char str_hour[3];     //---e.g. "15\0"---
char str_minute[3];   //---e.g. "59\0"---
char str_second[3];   //---e.g. "30\0"---
char str_current_hourmin[6];  //---store hr:min; e.g. 15:59\0---

//---add in the following statements---
void my_layer_update_proc(Layer *my_layer, GContext* ctx) {
    //---draw 2 rectangles to represent the battery---
    GRect rect1;
    rect1.origin = GPoint(5,15);
    rect1.size = GSize(54,20);
    graphics_draw_rect(ctx, rect1);

    GRect rect2;
    rect2.origin = GPoint(58,20);
    rect2.size = GSize(5,10);
    graphics_draw_rect(ctx, rect2);

    //---draw a filled rectangle to represent the battery level---
    GRect rect3;
    rect3.origin = GPoint(7,17);
    rect3.size = GSize(50,16);
    graphics_fill_rect(ctx, rect3, 0, GCornerNone);
}
//---end of statements to add---

static void handle_second_tick(
...
}

...

static void window_load(Window *window) {
    Layer *window_layer = window_get_root_layer(window);
    GRect bounds = layer_get_bounds(window_layer);

    text_layer = text_layer_create((GRect) {
        .origin = { 0, 42 },
        .size = { bounds.size.w, 42 } });

    text_layer_set_text(text_layer, "Press a button");
    text_layer_set_text_alignment( text_layer, GTextAlignmentCenter);

    text_layer_set_font(text_layer,
      fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD));

    text_layer_second = text_layer_create((GRect) {
        .origin = { 0, 90 },
        .size = { bounds.size.w, 28 }
    });

    text_layer_set_text_alignment(
      text_layer_second, GTextAlignmentCenter);
    text_layer_set_font(text_layer_second,
      fonts_get_system_font(FONT_KEY_GOTHIC_28));

    layer_add_child(window_layer, text_layer_get_layer(text_layer));

    layer_add_child(window_layer, text_layer_get_layer(text_layer_second));

    inverter_layer = inverter_layer_create(
      GRect(0, 0, bounds.size.w, bounds.size.h));

    //---add in the following statements---
    //---create the layer and set its update function---
    mydrawings_layer = layer_create(bounds);
    layer_set_update_proc(mydrawings_layer, my_layer_update_proc);

    //---add the layer to the Window layer---
    layer_add_child(window_layer, mydrawings_layer);
    //---end of statements to add---
}

static void window_unload(Window *window) {
    text_layer_destroy(text_layer);
    text_layer_destroy(text_layer_second);
    inverter_layer_destroy(inverter_layer);

    //---add in the following statements---
    layer_destroy(mydrawings_layer);
    //---end of statements to add---
}

To draw on the Window, you need to create a Layer. The layer_set_update_proc() function allows you to set a function to be called automatically whenever the layer needs to be redrawn. In this case, the function is called my_layer_update_proc(). In the my_layer_update_proc() function, you draw the first two rectangles to represent the battery, and a third rectangle to represent the battery level (see Figure 15).

Figure       15      : Use rectangles to represent a battery.
Figure 15 : Use rectangles to represent a battery.

The width of rect3 is set to 50 pixels for a full battery level. Figure 16 shows how the drawings look when you deploy the application onto the Pebble.

Figure       16      : The drawings representing the battery level
Figure 16 : The drawings representing the battery level

When the screen is inverted, it looks like Figure 17.

Figure       17      : The screen when inverted
Figure 17 : The screen when inverted

Monitoring the Battery Level

Now that you've drawn the rectangles representing the battery, add in the code to monitor the battery level of the Pebble. Add the highlighted code to the MyWatch.c file as shown in Listing 5.

Listing 5: Monitoring battery levels

#include <pebble.h>

static Window *window;
static TextLayer *text_layer;
static TextLayer *text_layer_second;

static InverterLayer *inverter_layer;

static Layer *mydrawings_layer;

//---add in the following statements---
//---represent the battery charge state---
BatteryChargeState batteryState;
//---end of statements to add---

char str_hour[3];             //---e.g. "15\0"---
char str_minute[3];           //---e.g. "59\0"---
char str_second[3];           //---e.g. "30\0"---
char str_current_hourmin[6];  //---store hr:min; e.g. 15:59\0---

//---add in the following statements---
//---fired when there is a change in battery charge
// state---
static void battery_state_handler(BatteryChargeState charge) {
    //---mark the drawing layer as dirty so as to force
    // a redraw---
    layer_mark_dirty(mydrawings_layer);
}
//---end of statements to add---

void my_layer_update_proc(Layer *my_layer, GContext* ctx) {
    //---draw 2 rectangles to represent the battery---
    GRect rect1;
    rect1.origin = GPoint(5,15);
    rect1.size = GSize(54,20);
    graphics_draw_rect(ctx, rect1);

    GRect rect2;
    rect2.origin = GPoint(58,20);
    rect2.size = GSize(5,10);
    graphics_draw_rect(ctx, rect2);

    GRect rect3;
    rect3.origin = GPoint(7,17);

    //---add in the following statements---
    //rect3.size = GSize(50,16);
    //---change the width of the rect to match the battery level---
    rect3.size = GSize(batteryState.charge_percent/2, 16);
    //---end of statements to add---

    graphics_fill_rect(ctx, rect3, 0, GCornerNone);}
}

static void window_load(Window *window) {
    Layer *window_layer = window_get_root_layer(window);
    GRect bounds = layer_get_bounds(window_layer);

    text_layer = text_layer_create((GRect) {
        .origin = { 0, 42 },
        .size = { bounds.size.w, 42 } });

    text_layer_set_text(text_layer, "Press a button");
    text_layer_set_text_alignment(
      text_layer, GTextAlignmentCenter);

    text_layer_set_font(text_layer,
      fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD));

    text_layer_second = text_layer_create((GRect) {
        .origin = { 0, 90 },
        .size = { bounds.size.w, 28 }
    });
    text_layer_set_text_alignment(text_layer_second, 
      GTextAlignmentCenter);
    text_layer_set_font(text_layer_second,
      fonts_get_system_font(FONT_KEY_GOTHIC_28));

    layer_add_child(window_layer,
      text_layer_get_layer(text_layer));

    layer_add_child(window_layer,
      text_layer_get_layer(text_layer_second));

    inverter_layer = inverter_layer_create(
      GRect(0, 0, bounds.size.w, bounds.size.h));

    //---add in the following statements---
    //---get the charge state of the battery---
    batteryState = battery_state_service_peek();
    //---end of statements to add---

    mydrawings_layer = layer_create(bounds);
    layer_set_update_proc(mydrawings_layer, my_layer_update_proc);
    layer_add_child(window_layer, mydrawings_layer);
}

...

static void init(void) {
    window = window_create();
    window_set_click_config_provider(
      window, click_config_provider);
    window_set_window_handlers(window, (WindowHandlers) {
        .load = window_load,
        .unload = window_unload,
    });

    tick_timer_service_subscribe(SECOND_UNIT, handle_second_tick);

    //---add in the following statements---
    //---subscribe to the battery charge state event
    // service---
    battery_state_service_subscribe(battery_state_handler);
    //---end of statements to add---

    const bool animated = true;
    window_stack_push(window, animated);
}

static void deinit(void) {
    tick_timer_service_unsubscribe();

    //---add in the following statements---
    //---unsubscribe from the battery charge state event
    // serice---
    battery_state_service_unsubscribe();
    //---end of statements to add---

    window_destroy(window);
}

The BatteryChargeState structure contains three members:

  • charge_percent allows you to know how full the battery is (0 to 100).
  • is_charging allows you to know if the battery is currently being charged.
  • is_plugged allows you to know if the charger cable is connected.

To know the current battery state, use the battery_state_service_peek() function. To subscribe to changes in the battery level, use the battery_state_service_subscribe() function and pass it a function name. In this case, the function is battery_state_handler. To refresh the drawings, call the layer_mark_dirty() function and pass it to the layer that you wish to redraw. This function automatically calls the my_layer_update_proc() function to update the drawings representing the battery. Deploy the application onto your Pebble and you'll be able to see the battery level.

The maximum size of a Pebble app is 100KB, including the app's resource and binary files.

Converting the Application into a Watchface App

By default, your application (known as a watch app) allows interaction with the user through the three buttons on the right edge of the Pebble. However, there are times when the user doesn't need to interact with your application, which runs without user input. This is known as a Watchface app. A Watchface app is designed to run for a long period of time, and usually provides information (such as time, weather, etc.) at a glance.

To convert your application from a watch app to a Watchface app, set the watchface attribute in the appinfo.json file to true and rebuild the application:

{
    "uuid": "ad7b91ec-3b37-4e14-950d-2ebff36295fe",
    "shortName": "MyWatch",
    "longName": "MyWatch",
    "companyName": "MakeAwesomeHappen",
    "versionCode": 1,
    "versionLabel": "1.0.0",
    "watchapp": {
        "watchface": true
    },
    "appKeys": {
        "dummy": 0
    },
    "resources": {
        "media": [
            {
                "type": "png",
                "menuIcon": true,
                "name": "IMAGE_MENU_ICON",
                "file": "images/icon.png"
            }
        ]
    }
}

When the application is deployed to the Pebble, you'll realize that the system status bar (at the top) is now gone (see Figure 18). What's more, it can now be a default app when the application menu times out. However, pressing the Up or Down button switches between the other Watchface apps on the Pebble and pressing the Select button invokes the application menu.

Figure       18      : The application is now running as a Watchface app.
Figure 18 : The application is now running as a Watchface app.

Interacting with the Outside World

As discussed earlier, the only connectivity option the Pebble has is its Bluetooth connection to the mobile device. It doesn't have GPSor WiFi, nor does it have cellular network connectivity. The only way for your Pebble application to communicate with the outside world is through the mobile device.

A Watchface app is designed to run for a long period of time, and usually provides information (such as time, weather, etc.) at a glance. Unlike a watch app, a Watchface app does not interact with the users through the three buttons.

There are a couple of options available. Figure 19 shows the first option: writing native apps on iPhone or Android devices. These native apps can use the PebbleKit for iOS or the PebbleKit for Android Frameworks to communicate with the Pebble. Using this approach, your native app can connect to the Internet and consume services, connect to socket servers, etc., and relay the data back to the Pebble.

Figure       19      : Write a native app to help the Pebble app communicate with the outside world
Figure 19 : Write a native app to help the Pebble app communicate with the outside world

Although this option sounds interesting, it requires you to write native apps for both iOS and Android.

Beginning with Pebble 2.0, you can now write your code using a feature known as PebbleKit for JavaScript. Using the PebbleKit for JavaScript, you write your Pebble app in C and an additional app in JavaScript. Here is how it works:

  • Bundle your JavaScript code together with your Pebble app.
  • The Pebble Mobile app extracts and installs the JavaScript code during app installation.
  • The JavaScript code runs within a sandbox inside the Pebble Mobile app on the Android or iOS device.
  • Your Pebble app starts a request to run the JavaScript code on the phone; JavaScript code is killed when your Pebble app exits.
  • The JavaScript code can perform tasks such as consuming Web services, geo-location, persistence storage, etc.

Figure 20 summarizes the flow of activities between the Pebble app and the JavaScript code.

Figur 20: The Pebble app communicates with the JavaScript code
Figur 20: The Pebble app communicates with the JavaScript code

The advantages of using the PebbleKit for JavaScript are:

  • It's completely platform-independent. You don't have to write separate native code for Android and iOS.
  • Your app is easy to install; users need not download additional mobile apps.

Your JavaScript code can:

  • Access Internet and GPS on the phone
  • Use the phone screen and keyboard to display a configuration interface
  • Persist data on the phone
  • Do other things that you can do with JavaScript

Using PebbleKit for JavaScript

The best way to understand how PebbleKit for JavaScript works is to create a simple example. In Terminal, type in the following command to create a project named helloJS:

MyMac:pebble-dev wei-menglee$ pebble new-project
--javascript helloJS

Examine the helloJS folder created. Besides the usual suspects, you should also see a folder named js that contains a file named pebble-js-app.js (see Figure 21).

Figure       21: The JavaScript code file inside the Pebble project
Figure 21: The JavaScript code file inside the Pebble project

Defining the Keys

In order to facilitate communications between the Pebble app and JavaScript code, data is exchanged using key/value pairs. You first need to define the keys that you're going to use in your app.

Add the following statements to the appKeys attribute in the appinfo.json file:

{
    "uuid": "0ada7161-4ce5-4a88-9332-8b93b9a9d9cc",
    "shortName": "helloJS",
    "longName": "helloJS",
    "companyName": "MakeAwesomeHappen",
    "versionCode": 1,
    "versionLabel": "1.0.0",
    "watchapp": {
        "watchface": false
    },
    "appKeys": {
        "key1": 5,
        "key2": 6,
        "key3": 7,
        "key4": 8
    },
    "resources": {
        "media": []
    }
}

In the above snippet, you defined the keys that you want to use in your application. In this example, to send data using a key/value pair, you can either use “key1” or 5 as the key. Similarly, you can either use “key2” or 6 as the key for another key/value pair, and so on. The values 5, 6, 7, and 8 used in this example are purely arbitrary; you can use any numbers you want for the keys. The use of either the string “key1” or the number 5 will be evident later on when you examine the code for sending and receiving data.

Sending a Message from the Pebble to JavaScript

The first thing you need to learn is how to get the Pebble app to send a message to the JavaScript code running on the Pebble Mobile app. Add the highlighted statements to the helloJS.c file as shown in Listing 6:

Listing 6: Sending a Message from Pebble to JavaScript

#include <pebble.h>

static Window *window;
static TextLayer *text_layer;

//---add in the following statements---
void out_sent_handler(DictionaryIterator *sent, void *context) {
    APP_LOG(APP_LOG_LEVEL_DEBUG, "[PEBBLE] Message delivered");
}

void out_failed_handler(
  DictionaryIterator *failed, AppMessageResult reason, void *context) {
    APP_LOG(APP_LOG_LEVEL_DEBUG, "[PEBBLE] Message delivery failed");
}

void in_received_handler(DictionaryIterator *received, void *context) {}

void in_dropped_handler(AppMessageResult reason, void *context) {}
//---end of statements to add---

static void select_click_handler(
  ClickRecognizerRef recognizer, void *context) {
    text_layer_set_text(text_layer, "Select");

    //---add in the following statements---
    DictionaryIterator *iter;
    app_message_outbox_begin(&iter);

    //---5 (key1) defined in appinfo.json---
    Tuplet value1 = TupletInteger(5, 35);

    //---6 (key2) defined in appinfo.json---
    Tuplet value2 = TupletCString(6, "A string");

    dict_write_tuplet(iter, &value1);
    dict_write_tuplet(iter, &value2);

    app_message_outbox_send();
    //---end of statements to add---
}

...

static void init(void) {
    window = window_create();
    window_set_click_config_provider(window, click_config_provider);
    window_set_window_handlers(window, (WindowHandlers) {
        .load = window_load,
        .unload = window_unload,
    });

    //---add in the following statements---
    app_message_register_inbox_received(in_received_handler);
    app_message_register_inbox_dropped(in_dropped_handler);

    app_message_register_outbox_sent(out_sent_handler);
    app_message_register_outbox_failed(out_failed_handler);

    //---size of the inbox and outbox buffers (bytes)---
    const uint32_t inbound_size = 64;
    const uint32_t outbound_size = 64;

    app_message_open(inbound_size, outbound_size);
    //---end of statements to add---

    const bool animated = true;
    window_stack_push(window, animated);
}

In order for your Pebble app to communicate with the JavaScript code running on the Pebble Mobile app, you need to define a few functions to receive and send data:

  • Use the app_message_register_inbox_received() function to register a handler for incoming messages.
  • Use the app_message_register_inbox_dropped() function to register a handler for dropped incoming messages.
  • Use the app_message_register_outbox_sent() function to register a handler for messages that are sent successfully.
  • Use the app_message_register_outbox_failed() function to register a handler for messages that have failed to send.
  • Use the app_message_open() function to open a bi-directional communication channel between the Pebble app and the Pebble Mobile app, passing it the required size for the Inbox and Outbox buffers.

To send data to the JavaScript code, you use a DictionaryIterator structure. A DictionaryIterator structure contains key/value pair Tuplets (a Tuplet is a helper data structure to create a Tuple or key/value pair). You use the app_message_outbox_begin() function to begin writing to the outbox's Dictionary buffer. You use the app_message_outbox_send() function to send a DictionaryIterator variable.

Receiving Messages on JavaScript

To receive a message on the JavaScript side, add the highlighted statements to the pebble-js-app.js file located in the js folder of the src folder (see Listing 7).

Listing 7: Receiving Messages on JavaScript

Pebble.addEventListener("ready", function(e) {
    console.log("Hello world! - Sent from your javaScript application.");
});

//---add in the following statements---
Pebble.addEventListener("appmessage", function(e) {
    console.log("[JAVASCRIPT] Received message from PEBBLE: " +
      JSON.stringify(e.payload));
    console.log("[JAVASCRIPT] ---key1: " + e.payload.key1);
    console.log("[JAVASCRIPT] ---key2: " + e.payload.key2);
});
//---end of statements to add---

When the Pebble opens the communication channel to communicate with the JavaScript, the JavaScript code receives the “ready” event. When a message is received on the JavaScript, it receives the “appmessage” event. You can retrieve the data that is received on the JavaScript side using the format: e.payload.<keyname>.

Earlier, you defined four keys: key1 (value 5), key2 (value 6), key3 (value 7), and key4 (value 8). The string values of the keys (“key1”, “key2”, etc.) are used in JavaScript, while their corresponding values (5, 6, etc.) is used in the C code.

Build and deploy the application onto the Pebble and observe the output:

MyMac:helloJS wei-menglee$ pebble install --logs
--phone 192.168.2.105
[INFO    ] Enabling application logging...
[INFO    ] Installation successful
[INFO    ] I app_manager.c:138 Heap Usage for
<MyWatch>: Total Size <21960B> Used <364B> Still
allocated <0B>
[INFO    ] D helloJS.c:95 Done initializing, pushed
window: 0x2001a748
[INFO    ] Displaying logs ... Ctrl-C to interrupt.
[INFO    ] JS: starting app: 0ADA7161-4CE5-4A88-9332-
8B93B9A9D9CC helloJS
[INFO    ] app is ready: 1
[INFO    ] JS: helloJS: Hello world! - Sent from your
javascript application.

The output shows that the Ready message was received by the JavaScript. Press the Select button on the Pebble and observe the output printed in the Terminal application:

[INFO    ] JS: helloJS: [JAVASCRIPT] Received message
from PEBBLE: {"key2":"A string","key1":35}
[INFO    ] JS: helloJS: [JAVASCRIPT] ---key1: 35
[INFO    ] JS: helloJS: [JAVASCRIPT] ---key2: A string
[INFO    ] D helloJS.c:7 [PEBBLE] Message delivered

The output shows that the message was sent successfully from the Pebble to the JavaScript. The JavaScript has also successfully received the two key/value pairs.

Sending a Message from JavaScript to Pebble

The next step is for the JavaScript to send back a message to the Pebble. For example, the JavaScript may consume a Web service and return the result to the Pebble.

Add the highlighted statements to the pebble-js-app.js file (see Listing 8).

Listing 8: Sending a Message to the Pebble

Pebble.addEventListener("ready", function(e) {
    console.log("Hello world! - Sent from your javascript application.");
});

Pebble.addEventListener("appmessage", function(e) {
    console.log("[JAVASCRIPT] Received message from PEBBLE: " +
      JSON.stringify(e.payload));
    console.log("[JAVASCRIPT] ---key1: " + e.payload.key1);
    console.log("[JAVASCRIPT] ---key2: " + e.payload.key2);

    //---add in the following statements---
    //---send message to Pebble---
    sendMessageToPebble();
    //---end of statements to add---
});

//---add in the following statements---
//---send message to Pebble---
function sendMessageToPebble() {
    var transactionId = Pebble.sendAppMessage(
        {
            "key3": 25,
            "key4": "Some string value"
        },
        //---fired when message is delivered---
        function(e) {
            console.log("[JAVASCRIPT] Successfully delivered message " +
              "with transactionId=" + e.data.transactionId);
        },

        //---fired when message fails to deliver---
        function(e) {
            console.log("[JAVASCRIPT] Unable to deliver message " +
              "with transactionId=" + e.data.transactionId +
              " Error is: " + e.error.message);
         });
    }
//---end of statements to add---

To send a message back to the Pebble app, use the Pebble.sendAppMessage() function. The data to be sent back is formulated as a JSON string.

Receiving Messages on the Pebble

To receive the message sent by the JavaScript to the Pebble app, add the highlighted statements to the helloJS.c file (see Listing 9):

Listing 9: Receiving Messages on the Pebble

void in_received_handler(
  DictionaryIterator *received, void *context) {

    //---add in the following statements---
    APP_LOG(APP_LOG_LEVEL_DEBUG, "[PEBBLE] Message received");

    //---extract the value for each key---

    //---key3 corresponds to 7---
    Tuple *int_tuple = dict_find(received, 7);

    //---key4 corresponds to 8---
    Tuple *text_tuple = dict_find(received, 8);

    if (int_tuple) {
        APP_LOG(APP_LOG_LEVEL_DEBUG, 
          "[PEBBLE] From JAVASCRIPT - key3: %d",
          int_tuple->value->int16);
    }

    if (text_tuple) {
        APP_LOG(APP_LOG_LEVEL_DEBUG,
          "[PEBBLE] From JAVASCRIPT - key4: %s",
          text_tuple->value->cstring);
    }
    //---end of statements to add---
}

void in_dropped_handler(AppMessageResult reason, void *context) {
    //---add in the following statements---
    APP_LOG(APP_LOG_LEVEL_DEBUG,
      "[PEBBLE] Incoming message dropped");
      //---end of statements to add---
}

When the Pebble app receives the message, the data is passed in via a DictionaryIterator variable. You then use the dict_find() function to retrieve each Tuple.

Deploy the application, press the Select button on the Pebble, and observe the output printed in the Terminal application:

[INFO    ] JS: helloJS: [JAVASCRIPT] Received message
from PEBBLE: {"key2":"A string","key1":35}
[INFO    ] JS: helloJS: [JAVASCRIPT] ---key1: 35
[INFO    ] JS: helloJS: [JAVASCRIPT] ---key2: A string
[INFO    ] D helloJS.c:16 [PEBBLE] Message received
[INFO    ] D helloJS.c:24 [PEBBLE] From JAVASCRIPT �
key3: 25
[INFO    ] D helloJS.c:29 [PEBBLE] From JAVASCRIPT �
key4: Some string value
[INFO    ] JS: helloJS: [JAVASCRIPT] Successfully
delivered message with  transactionId=0
[INFO    ] D helloJS.c:7 [PEBBLE] Message delivered

The output demonstrates that the message sent by the JavaScript back to the Pebble is received successfully on the Pebble side.

Consuming a Web Service using JavaScript

The previous section touched on the communication pattern between the Pebble and the JavaScript. To make the example really useful, it would be good to make the JavaScript consume a Web service to demonstrate the usefulness of PebbleKit for JavaScript.

In this section, let's modify the pebble-js-app.js file to make it consume a Yahoo Web service that retrieves the prices of some stock (see Listing 10).

Listing 10: Accessing web service on the JavaScript

Pebble.addEventListener("ready", function(e) {
  console.log("Hello world! - Sent from your javascript application.");
});

Pebble.addEventListener("appmessage", function(e) {
    console.log("[JAVASCRIPT] Received message from PEBBLE: " +
      JSON.stringify(e.payload));
    console.log("[JAVASCRIPT] ---key1: " + e.payload.key1);
    console.log("[JAVASCRIPT] ---key2: " + e.payload.key2);

    //---add in the following statements---
    //sendMessageToPebble();
    getStockPrice();
    //---end of statements to add---
});

//---send message to Pebble---
//---add in the following statements---
//---this function now accepts an input parameter---
function sendMessageToPebble(result) {
    //---end of statements to add---
    var transactionId = Pebble.sendAppMessage(
        {
             "key3": 25, //---add in the following statements---
             "key4": result  //---send the result back---
             //---end of statements to add---
        },
        function(e) {
            console.log("[JAVASCRIPT] Successfully delivered message " +
              "with transactionId=" + e.data.transactionId);
        },
        function(e) {
            console.log("[JAVASCRIPT] Unable to deliver message " +
              "with transactionId=" + e.data.transactionId +
               " Error is: " + e.error.message);
        });
}

//---add in the following statements---
//---access the Yahoo web service---
function getStockPrice() {
    var req = new XMLHttpRequest();
    req.open('GET','http://query.yahooapis.com/v1/public/yql?q=' +
      'select%20*%20from%20yahoo.finance.quotes%20' +
      'where%20symbol%20in%20(%22YHOO%22%2C%22' +
      'AAPL%22)%0A%09%09&env=http%3A%2F%2F'+
      'datatables.org%2Falltables.env&format=json', true);

    req.onload = function(e) {
        if (req.readyState == 4 && req.status == 200) {
            if(req.status == 200) {
                var response = JSON.parse(req.responseText);
                var result = "";
                for (i=0; i<response.query.count; i++) {
                    result +=response.query.results.quote[i].Symbol +
                      " - $" +
                       response.query.results.quote[i].Bid + "\n";
                 }
                 } else {
                     result = "Error contacting web service";
                 }
                 //---send the stock price back to the Pebble---
                 sendMessageToPebble(result);
        }
    }
    req.send(null);
}
//---end of statements to add---

You use the XMLHttpRequest class method to connect to the Web service and fetch its result. Once the result is obtained, you extract the stock prices and send them back to the Pebble.

In the helloJS.c file, add the highlighted code as shown in Listing 11.

Listing 11: Displaying the result of the web service in the text layer

void in_received_handler(
  DictionaryIterator *received, void *context) {
    APP_LOG(APP_LOG_LEVEL_DEBUG, "[PEBBLE] Message received");

    //---extract the value for each key---
    //---key3 corresponds to 7---
    Tuple *int_tuple = dict_find(received, 7);

    //---key4 corresponds to 8---
    Tuple *text_tuple = dict_find(received, 8);

    if (int_tuple) {
        APP_LOG(APP_LOG_LEVEL_DEBUG,
          "[PEBBLE] From JAVASCRIPT - key3: %d",
          int_tuple->value->int16);
    }

    if (text_tuple) {
        APP_LOG(APP_LOG_LEVEL_DEBUG,
          "[PEBBLE] From JAVASCRIPT - key4: %s",
          text_tuple->value->cstring);

        //---add in the following statements---
        //---displays the result in the text layer---
        text_layer_set_text(text_layer, text_tuple->value->cstring);
        //---end of statements to add---
    }
}

static void window_load(Window *window) {
    Layer *window_layer = window_get_root_layer(window);
    GRect bounds = layer_get_bounds(window_layer);

    text_layer = text_layer_create((GRect) {
        .origin = { 0, 72 },
        //---add in the following statements---
        .size = { bounds.size.w, 40 } }); //---increase the height---

        //---end of statements to add---
        text_layer_set_text(text_layer, "Press a button");
        text_layer_set_text_alignment(text_layer, GTextAlignmentCenter);
        layer_add_child(window_layer, text_layer_get_layer(text_layer));
}

Deploy the application and press the Select button on the Pebble once the application is running. After a while, you should see the stock prices on the Pebble (see Figure 22).

Figure       22      : The result returned by the Web service is shown on the Pebble.
Figure 22 : The result returned by the Web service is shown on the Pebble.

Summary

In this article, I've taken you on a whirlwind tour of how to develop for the Pebble. You've learned how to create a simple Pebble application, build it, and then deploy it onto the Pebble. You've also learned how to:

  • Handle button clicks on the Pebble
  • Display the current time
  • Invert the screen
  • Draw shapes on the Pebble
  • Monitor for battery levels on the Pebble
  • Use PebbleKit for JavaScript to extend the functionalities of your Pebble app
  • Consume Web services using JavaScript

Hopefully, with this article you'll now be able to create some interesting Pebble apps!