Development Pebble SDK Watchface Tutorial

Development

Pebble SDK Watchface Tutorial

"I was an early backer of the Pebble: E-Paper Watch for iPhone and Android Kickstarter project. I received my Pebble in February – it isn't perfect, but it's a favorite watch of mine (I love switching the watchfaces). The Pebble team definitely deserves credit for their role in kickstarting the wearable computing craze.

Pebble ships with a variety of watchfaces, I switch them frequently to keep my Pebble experience fresh. More watchfaces – and watchapps – are being developed. Check out the Pebble Watchapp Directory.

At Concentric Sky, we design, engineer and deliver digital experiences for many different devices, so we jumped at the opportunity to develop a watchface for Pebble. Our design team based our watchface on the Concentric Sky logo. Wiggins, the project's developer, put together a tutorial that includes great development tips for the Pebble Watchface SDK; we've made the code available on GitHub. We hope you find it useful."

Cale Bruckner, Vice President

The Pebble SDK is an arm cross-compiler toolchain. The tools are written in Python, so a Python 2.7 environment is required – I used mkvirtualenv for simplicity. 

My development machine was an OS X 10.6 MacBook Pro. This particular machine shipped with Python 2.6, so I needed to upgrade to 2.7. That also meant I needed to compile the arm-eabi-toolchain, which is simple with Xcode installed.

This tutorial assumes you have the latest Xcode installed. Use the App Store to download and install the latest version. There is a command line only version of the Xcode tools, but I reccommend installing Xcode because it also installs a GNU compiler toolchain along with other useful command line tools.

This tutorial also depends on Homebrew, it's suggested by the Pebble SDK install documentation and used here for succinctness.

I provide a quick overview of my SDK installation steps. I gleaned these from the official SDK documentation. Get started.

Installing the SDK

  1. Install required libraries:
          % brew install mpfr gmp libmpc libelf texinfo freetype
          % brew link --force freetype
        
  2. Install Python 2.7:
          % brew install python --universal --framework
          % easy_install pip
        
  3. Crate a Python virtualenv:
          % pip install mkvirtualenv
          % mkvirtualenv pebble-sdk
        
  4. Compile the toolchain:
          % mkdir -p $HOME/pebble/src; cd $HOME/pebble/src
          % git clone git://github.com/pebble/arm-eabi-toolchain.git
          % PREFIX=$HOME/pebble/arm-cs-tools make install-cross
        

    I make the directory $HOME/pebble to house all my Pebble related files.
    This installs the toolchain to $HOME/pebble/arm-cs-tools/.

  5. Install the SDK:
          % cd $HOME/pebble
          % unzip ~/Downloads/pebble-sdk-release-001.zip
          % workon pebble-sdk
          % pip install -r $HOME/pebble/pebble-sdk-release-001
          % pip install PIL
        

    Install the SDK at $HOME/pebble/pebble-sdk-release-001

  6. Update your $PATH:

    Add the following to your $HOME/.bash_profile:

            PEBBLE_SDK_HOME=$HOME/pebble/pebble-sdk-release-001
            PEBBLE_TOOLCHAIN_HOME=$HOME/pebble/arm-cs-tools
            PEBBLE_SDK=$PEBBLE_SDK_HOME/sdk
            PATH=$PEBBLE_SDK:$PEBBLE_SDK_HOME/tools:$PEBBLE_TOOLCHAIN_HOME/bin:$PATH
            alias create_pebble_project="create_pebble_project.py $PEBBLE_SDK"
            alias update_pebble_project="create_pebbble_project.py --symlink-only $PEBBLE_SDK"
        

     

    Add 3 directories to $PATH: $HOME/pebble/pebble-sdk-release-001/sdk, $HOME/pebbel/pebble-sdk-release-001/tools, and $HOME/pebble/arm-cs-tools/bin.

    Also create two alises for the create_pebble_project and update_pebble_project to include the SDK paths automatically.

The SDK should now be ready. More direction can be found in the official documentation or at the developer forums.

Before using the SDK, activate the Python virtualenv, and update your path to include the SDK:

    % workon pebble-sdk
    % source ~/.bash_profile
  

I also create a publish folder to make it easier to install and test my code. I run this in a seperate minimized terminal session:

    % mkdir -p $HOME/pebble/publish
    % cd $HOME/pebble/publish
    % python -m SimpleHTTPServer 8000
  

Starting a New Project

The full source code for this tutorial can be found on GitHub: concentricsky/pebble-watchface-tutorial.

First step is to bootstrap the project. Make sure virtualenv is enabled and SDK is in the path:

    % . ~/.bash_profile
    % workon pebble-sdk
  

Now create the project, and use 'waf configure' to create the SDK links:

    % cd $HOME/pebble
    % create_pebble_project tutorial-watchface
    % cd tutorial-watchface
    % ./waf configure
  

Don't forget to commit!

    % git init; git add -A; git commit -m "initial"
  

The project should now build, I use this command:

    % ./waf clean build && cp build/tutorial-watchface.pbw $HOME/pebble/publish
  

This will always clean old objects, build, and copy the build file to the publish folder. This can be repeated with ease, it allows a fresh build and it is just one click away from publishing. Bash Tip: Use 'Ctrl+R publish' to find and re-run this command easily

A Basic Watchface Project

All Pebble apps share this similar program structure. Please keep the following in mind; 1) there is no dynamic memory allocation, 2) all variables must be defined statically at compile time, this can be a tricky exercise for someone who is used to dynamicaly interpreted languages and 3) the global variable is valuable and useful – use it. It is generally bad code design to use global variables. However, there are no classes – and likely only one source file – so global variables prove helpful. My convention here is to use g_ as a prefix for global variables.

src/tutorial-watchface.c – Preamble

  #include "pebble_os.h"
  #include "pebble_app.h"
  #include "pebble_fonts.h"

  #define MY_UUID { 0xBD, 0x46, 0x1D, 0x5E, 0xCA, 0x85, 0x40, 0xF3, 0xB3, 0x63, 0x15, 0x99, 0xA1, 0x42, 0x29, 0x8F }
  PBL_APP_INFO(MY_UUID,
               "TemplateWatch", "Concentric Sky",
               1, 0, /* App version */
               RESOURCE_ID_IMAGE_MENU_ICON,
               APP_INFO_STANDARD_APP);

  /*
   * Global Variable Definitions
   */
  Window g_Window;
  

MY_UUID is generated by the SDK, leave it as is. Update the PBL_APP_INFO with the appropriate info. RESOURCE_ID_IMAGE_MENU_ICON references an icon mentioned in the resources file later.

src/tutorial-watchface.c – Second Handler

    void handle_second_tick(AppContextRef ctx, PebbleTickEvent *t) {
      (void)ctx;
      (void)t;
    }
  

This will get filled in later.

src/tutorial-watchface.c – handle_init

  void handle_init(AppContextRef ctx) {
    (void)ctx;

    window_init(&g_Window, "Window Name");
    window_stack_push(&g_Window, true /* Animated */);
    window_set_background_color(&g_Window, GColorBlack);
    resource_init_current_app(&APP_RESOURCES);
  }
  

Initialize the window and add it to the stack. Set the background color to black. Initialize the resources, note that APP_RESOURCES matches the versionDefName in resource_map.json.

src/tutorial-watchface.c – Main Function

  void pbl_main(void *params) {
    PebbleAppHandlers handlers = {
      .init_handler = &handle_init,
      .tick_info = {
        .tick_handler = &handle_second_tick,
        .tick_units = SECOND_UNIT
      }
    };
    app_event_loop(params, &handlers);
  }
  

Set up the handlers and start the infinite event loop.

resources/src/resource_map.json

  {"friendlyVersion": "dev_0.0",
   "versionDefName": "APP_RESOURCES",
   "media": [
     {
      "type":"png",
      "defName":"IMAGE_MENU_ICON",
      "file":"images/menu_icon.png"
     }
   ]
  }
  

Define just one resource, our menu icon.

resources/src/images/menu_icon.png

A 24x28 2 color png. The colors are inverted when it is selected in the watch menu.

Fleshing It Out

Add a simple TextLayer, and print the date and time out to it.

src/tutorial-watchface.c – New Global Variables

  PblTm g_Now;              // the current time--updated by handle_second_tick
  TextLayer g_DateLayer;    // a text layer to print the date string

  // we need a static buffer for string_format_time
  #define _DATE_BUF_LEN 26
  static char _DATE_BUFFER[_DATE_BUF_LEN];
  #define DATE_FORMAT "%a %B %e %T"        // see the strftime(3) man page for options.
  

g_Now stores the current timestamp during a tick. Because it's a global variable, this means all functions can access it – an expensive operation, reduce unneeded calls to it.

A buffer to format the date and time is also needed – _DATE_BUFFER. The DATE_FORMAT never generates a string longer than 25 characters. I include an extra byte for a terminating null character.

src/tutorial-watchface.c – Initialize the Date Layer

  /* 
   * initialize the date layer
   */
  // our layer occupies the bottom 16px across the entire screen
  text_layer_init(&g_DateLayer, GRect(1, 168-16, 144, 16));
  text_layer_set_text_color(&g_DateLayer, GColorWhite);
  text_layer_set_background_color(&g_DateLayer, GColorClear);
  text_layer_set_text_alignment(&g_DateLayer, GTextAlignmentCenter);
  text_layer_set_font(&g_DateLayer, fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD));
  layer_add_child(&g_Window.layer, &g_DateLayer.layer);

  // trigger a second tick manually, to paint our initial frame.
  handle_second_tick(ctx,NULL);
  

Define the size of the text layer. This uses the bottom 16px across the screen. Also, set up the desired style for the text layer: white on transparent background, centered, Gothic, 14 and bold. Manually run handle_second_tick once at the end of handle_init to get the initial frame painted.

src/tutorial-watchface.c – Fill Out handle_second_tick

  void handle_second_tick(AppContextRef ctx, PebbleTickEvent *t) {
    (void)ctx;
    (void)t;

    // update our current time
    get_time(&g_Now);

    // formant and render the date string on the date layer
    string_format_time(_DATE_BUFFER, _DATE_BUF_LEN, DATE_FORMAT, &g_Now);
    text_layer_set_text(&g_DateLayer, _DATE_BUFFER);
  }
  

Fetch the latest time. Format the date and time into _DATE_BUFFER and print it to g_DateLayer.

This article was meant to help introduce the Pebble SDK. I wrote it to help familiarize people with the basics of the SDK, with the hope that it would be a springboard for further development. If you have any questions or comments, please add them below.

comments powered by Disqus