Pebble SDK 2.0 Tutorial #1: Your First Watchapp


After the Pebble SDK update to version 2.0, the time has come to write a new version of my tutorial series for this new SDK.

If you are not familiar with the following basic C concepts, please read Part 1 of the 1.X tutorial, which is just as appropriate now:

  1. Variables and variable scope (local or global?)
  2. Functions (definitions and use)
  3. Structures
  4. Pointers
  5. Program flow (if, else etc.)
  6. Pre-processor statements (#define, #include etc)

Up to speed? Good! Let’s make our first SDK 2.0 watchapp. This time the tutorial will take a more practical approach, with each section finishing with a compilable project showing off the things learned therein.

Development Environment

In order to write and compile watchfaces there are two main options for Windows users:

  1. Use Cloud Pebble.
  2. Install Ubuntu on a virtual machine.

Choose whichever works for you. However, since the first draft of this tutorial section, CloudPebble now allows you to install, take screenshots and see logs from Pebble apps from the Compilation page, so for the purposes of a more simple start I’ll be writing this tutorial from the Cloud Pebble perspective.

First Steps

To get started, log into Cloud Pebble and choose ‘Create Project’.

  1. Enter a suitable name such as ‘Tutorial Part 1’.
  2. Choose ‘Pebble C SDK’.
  3. Set the template to ‘Empty Project’
  4. Confirm with ‘Create’.

Next, click ‘New C file’ and enter a name. I’d recommend ‘main.c’. This file will contain the C code that makes up the watchface we’re creating.

Setting up the Basics

In order to use (or ‘include’) all the Pebble SDK goodness already created for our use, start the file with this line:

#include <pebble.h>

All C programs begin at the start of the ‘main’ function (void means no arguments). From there we call init() (initialize) to set up our app, app_event_loop() to wait for events such as ticks, buttons presses etc. Finally when the watchface is closed Pebble calls deinit() (de-initialize) to free up all the memory we’ve used.

int main(void)

In C all functions must be defined in full (behaviourally) or by prototype (just the signature, such as void init();) before their first call in a file. To meet this requirement and maintain simplicity for now, enter the next code segment ABOVE main(). This will be where we initialize all our app elements:

void init()
  //Initialize the app elements here!

void deinit()
  //De-initialize elements here to save memory!

Done that? Let’s add our first app element: the Window. In the previous 1.X SDK, these structures were declared globally and referred to elsewhere using pointers. Since then, the 2.0 SDK has moved to declaring a pointer, and then dynamically allocating memory for the element when they are created. This is much better from a memory management point of view.

Right at the top of the file, but UNDER the #include directive, add the pointer for the Window element. To keep it simple, we’ll just call it ‘window’:

Window *window;

At the moment, this ‘window’ pointer does nothing. The next step is to call the function to make this pointer point to a full fleshed out Window. Back inside init() add the following lines to create the Window element. For clarity, this one time I’ll re-write the whole init() function with the new code inserted:

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

You may be wondering what all this ‘window handlers’ business is about. When you open the main Pebble watch menu from any watch face, the screen that slides into view containing ‘Music, Set Alarm, Watchfaces etc.’ is a Window! It contains an element called a MenuLayer, but we will cover that type of element later. When a Window is created and filled with elements, those elements need to be created fully before they can be shown. These will be done in two ‘handler’ (in that they handle, or take care of a certain event) functions.

The two we will use to display something in our Window will be called window_load() and window_unload() respectively. It doesn’t require too much thought to realize what events they will handle! We will define them in a moment, but for now they are assigned by name to the .load and .unload members of the WindowHandlers type set to our Window for when it is loaded and unloaded.

Here are the basic starter definitions; once again they MUST be placed before their first calls in the file, so place them above init() but below the #include directive and global Window pointer declaration. If you did all that correctly, your c source file should look like this:

#include <pebble.h>

Window *window;

void window_load(Window *window)
  //We will add the creation of the Window's elements here soon!

void window_unload(Window *window)
  //We will safely destroy the Window's elements here!

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

void deinit()
  //De-initialize elements here to save memory!

int main(void)

If you haven’t arrived at this result so far, go back to the bits that differ and see how they ended up where they are.

The next step as a responsible app developer is to make sure we de-initialize anything we initialize so that the net memory we use after the app is quit is zero. To do this for the only element we have so far (a Window), add the following line so your deinit() function looks like this:

void deinit()
  //We will safely destroy the Window's elements here!

The final basic step is to actually make the app ‘appear’ when it is chosen from the watch menu. To do this, we call the window_stack_push() function, which ‘pushes’ our Window into the top of the stack, and so appearing on top and in the foreground. Add this line to the END of your init() function like so:

  window_stack_push(window, true);

This will make the window slide into view (denoted by the true argument), but it will be completely blank! Let’s rectify that.

Making the App Do Something

What we’ve done so far is to initialize an empty Window. Now let’s add the first sub-element to make it show some output. Introducing the TextLayer! This is an element used to show any standard string of characters into a pre-defined area. As with any element, we first need a global pointer. Add this under the Window pointer near the top of the file like so:

TextLayer *text_layer;

The next step is to allocate the memory for the underlying structure. This is done in the window_load() function. Here are the basic functions to call when setting up a TextLayer. I’ll summarize them afterwards:

  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), text_layer_get_layer(text_layer));

The first line calls the function to fully create the TextLayer structure with a frame size of 144 x 168 (stored in a GRect, or rectangle structure), origin at (0, 0), then assigns the location of the resulting memory to the text_layer pointer. The second line sets the layer’s background colour using the supplied pointer and GColor type. Finally the third line sets the text colour itself, in a similar manner to the second line. The fourth line adds the TextLayer to the Window as a child (shown in front).

After this, remember to add the corresponding de-initialization code to free up the memory we used in window_unload() but BEFORE window_destroy() (the elements that belong to a Window should be removed before the Window itself):


Now the best bit! We make the watch app tell us whatever we want. Go back to the line after layer_add_child() and add the next line. You can set the quoted text argument to whatever you want:

  text_layer_set_text(text_layer, "Anything you want, as long as it is in quotes!");

Now we should be ready to see the fruits of our labour!

Compilation and Installation

Make sure you hit ‘Save’, then click ‘Settings’ on the left. Here you can set all kinds of app-related settings, but for now just give your own values to ‘Short App Name’ and ‘Company Name’. Hit ‘Save Changes’. Next, go to ‘Compilation’ and hit ‘Run Build’. You should be greeted with ‘Successful’ next to ‘Status’. If not, go back and compare your code to the segments above, or check out the example download at the end of the post.

Enter your phone’s IP address and click ‘install and run’ after enabling Pebble Developer Connection or download the .pbw file, then open the resulting file on your phone. After the Pebble app has done it’s work, you should be able to see your text on the watch! Exciting!


So there you have it, a very simple watch app to show some text. Next time we’ll flesh it out a bit more and make it do something really useful, like showing the time!

You can find the example project that you should end up with after this tutorial section on GitHub.

As always let me know if you have any questions or suggestions and stay tuned for the next section!

  1. Ender said:

    AWESOME!!! You are the man. I really appreciate you taking the time to create these tutorials. I am looking forward to the rest of the series. It would be great if you created an app using the new Javascript http protocol since that is a very powerful new addition to the SDK that enables a bunch of new possibilities! I myself am trying to make an app to control the GPIO’s of my Raspberry Pi using Webiopi and REST commands.

    Again, great job on the tutorials and I hope you come out with a C + Javascript + REST tutorial in the future to help a brother out ๐Ÿ˜‰

    • bonsitm said:

      Hi Ender, thank you for the kind words. I’m working on Part 2 right now, which will be about telling the time and TextLayers etc. My Pebble Tube Status watchapp uses the PebbleJS API, but my JS experience is limited.

      I want to cover as much of the Pebble SDK as I can, so I will hopefully get as far as you are looking.

      Thanks again!

      • Ender said:

        Hey Chris,
        Do you have your Tube Status watchapp on Github or something? Would be nice to look at some code. I am currently reading Eloquent Javascript to start learning the language.

        If I can make the Pebble control the GPIO’s of my Raspberry Pi, this opens the door to control ANYTHING! I can control my home alarm, garage door, etc. It’s the key to controlling things with a watch! ๐Ÿ˜€

        I will be constantly checking your site for the next updates. Your tutorials are concise and simple and best of all, you don’t assume the reader knows anything which too many people do. Keep up the good work and style.

      • bonsitm said:

        Thanks again. It is not on GitHub but can be soon. I’m working on an update to my Selector app that will show all info on compatibility and availability which I will release soon. Stay tuned!

      • bonsitm said:

        And as luck would have it,I forgot to push the JS! I’ll commit it when I get home.

      • bonsitm said:

        Last time now. The JS file is now on GitHub. Enjoy!

  2. Ender said:

    Awesome! Looks like prime code for writing a tutorial on Javascript and SDK 2.0 ๐Ÿ˜‰

  3. Ender said:

    Do you have a video or pictures showing how your Tube Status app works? That would help in reverse engineering what your code does.

    • bonsitm said:

      I’m afraid I don’t. What are you unsure about?

      • Ender said:

        I was just curious. Don’t worry about it. There is nothing in particular I was unsure about. Thank you for making the source code available. Another data point to study ๐Ÿ˜€

        Like you said in one of your blog posts, exams are done so time to start cranking out those tutorials! We are all waiting! I believe you are the only person that has done a thorough SDK 2.0 tutorial thus far.

      • bonsitm said:

        Ender, part 2 is now available.

  4. Ender said:

    Correct me if I am wrong but you are studying Mechanical Engineering right? How is it that you learned all of this programming?

    • bonsitm said:

      Close, electronic engineering. I did a bit of C in the first year, now it’s a hobby of mine.

      • Ender said:

        Oh okay. You had said MEng in your About Me page so I figured that’s what you were studying. I did EE as well and also took some ME courses so I don’t know why I assumed that lol

        Going to read part 2 now. I am getting my Pebble watch for Christmas from my wife so I still do not have it. It’s paintful just waiting!!!! All I want to do is program stuff for it!

      • bonsitm said:

        An easy mistake to make. Best of luck! Only a few days to go. Start making notes of your ideas for when the time comes.

  5. Ender said:

    Notes of my ideas!? Forget that! I’ve started programming already! lol I am in the process of coding an app for a watch I have yet to use/own! This is why I need your tutorials asap. So that when I get my watch I can load my app and hopefully start using it right away (that never happens in programming so I will probably be debugging).

  6. Ender said:

    My Pebble watch arrived and my wife was awesome enough to not make me wait until Christmas to start using it so I just flashed the new 2.0 firmware on it and completed this tutorial. Everything worked flawlessly! Thanks again!!!!! On to part 2 now! I am loving this watch. I also used the site to create a watch face with the background image being that of my wife and she laughed quite a bit. ๐Ÿ™‚

  7. Ender said:

    I believe you may have overlooked something in this tutorial. You mention destroying the window in the window_unload function but you do not destroy the window in the deinit function. Then when you look at your final source code, you are destroying the window in both locations. Which one should it be in? Is there ever a time when window_unload will not be called when the app is closed where it is necessary to have the window destroy in deinit as well?

  8. Ender said:

    Upon further consideration, it seems to me as though only the text_layer_destroy should be in the window_unload function since the window_unload function is for destroying the window’s elements, not the window itself. Therefore, the text layer is destroyed in the window_unload and the window is destroyed in the deinit. Am I correct?

    • bonsitm said:

      Hi Ender, congrats and you are indeed correct! I must have copied it to window_load() and left it behind in deinit() by accident! I will correct the linked project as soon as I can.

      • Ender said:

        So it should be in window_unload and NOT in deinit? I would have thought it should be in deinit and NOT in window_unload since window_unload is for destroying the window’s elements, not the window itself. By the way, let me know if I am typing too many comments and there is another means by which you’d rather I send you messages (email? twitter?).

      • bonsitm said:

        Maybe twitter would be better. I confused myself again. The text layer should be destroyed in window_unload() and the window itself in deinit().

  9. AJ Hawks said:

    Great tutorial! Things like this are so simple once you understand a framework/environment, but starting from scratch it’s so helpful just to see how the pieces work together.

    • bonsitm said:

      Thanks AJ, that is the intended approach! Stay tuned for more.

  10. Patrick Fulton said:

    As new as you can be to coding,but I have a pebble and wanted to give this a try,I’ve done everything I can tell as far as it’s done on here but when compiling I get failed

    • bonsitm said:

      Hi Patrick,

      If you are on, in the ‘Compilation’ page click ‘Build log’. The lines in RED show what error occured and what line number is in error.

      For example: “../src/main.c:17:35: error: expected declaration specifiers before ‘e'” says that in file ‘main.c’ on line ’17’ (column ’35’), a new statement was expected, but instead it the compiler found ‘e’. This could be a typing error!

      What does your build log say?

      • Patrick Fulton said:

        Well I’ve got about 12 red lines,gonna start from scratch and give another go around,I think I may have got lost at a few parts,does anyone have a picture of the completed code to see where I went wrong?

      • bonsitm said:

        Hi Patrick, the full code for this part of the tutorial can be found here. Sometimes a simple error at the top of a source file can cause multiple errors below it. In one case, I managed to solve over 30 errors by fixing a function declaration!

  11. Patrick Fulton said:

    Awesome,thank you for your help. I’ll look it over and see if that helps
    (As to fixing 30 errors,that makes me feel much better ๐Ÿ™‚ )

  12. Patrick Fulton said:

    Well I see where most of my issues were,must have accidentally deleted some line,and one improper line,everything’s good now except I’m getting one error

    “Collect2: error: ld returned 1 exit status?

    • bonsitm said:

      Hi Patrick, programming is indeed very much an incremental process.

      I have not seen this error before, but after a Google search it appears it is related to Waf (the underlying compiling tool used by Pebble) and linking source files.

      My inexpert suggestion would be to see if there are any missing #include statements in your file.

  13. Patrick Fulton said:

    So I got it set up exactly like the full file you sent but it’s still giving me that error

    • bonsitm said:

      Hi Patrick,

      Is this the same Waf related error as earlier? If so this indicates a problem with the build tools setup on CloudPebble’s end. Since I could use it just fine just now, try copying your source C file into a brand new project and re-building from there.

      If it is a Pebble error, send me a link to a paste of your code and I’ll take a look at it!

  14. Patrick Fulton said:

    Ok rebuilding now,will let you know,thanks for the help

  15. Patrick Fulton said:

    Good news.that error went away in the new build,new error though
    “Implicit declaration of function ‘text_layer_destory’ and ‘window_destory”

    • bonsitm said:

      That should be ‘text_layer_destroy’. Have I left a type in the provided source? I’ll look at it now!

  16. Patrick Fulton said:

    No,I believe it was a spelling error on my part,fixed those problems using the completed file you sent,went to build and now I’m back to the issue of collect2: error: ld returned 1 exit status

    • bonsitm said:

      Oh dear, that is annoying. I’ll do some more Googling. It might also we worth trying the Pebble Forum Developer section.

  17. Patrick Fulton said:

    Ok thank you!

  18. Yob said:

    i can’t download it neither from the web, for from my phone. The short url doesnt work neither.

    • bonsitm said:

      Hi Yob, which download are you referring to?

  19. Hello, what a great service you are doing for people like myself wanting to create our own watchface! I am having some trouble with installing the watchface on the new android pebble app store (developer edition). I download the file to google drive, try to open it up on my phone, it starts downloading, then it says “loading to pebble failed.” I also tried the install & run on the compilation tab in cloudpebble but it gives me an error code 1.

    Could you please help. I’m guessing this has to do with the new pebble app store.


    • bonsitm said:

      Hi Blaine,


      I have not installed the Android 2.0 beta app with the store yet, although I would suggest you try downloading the .pbw file to your phone’s internal storage and opening it from there.

      If the build fails on cloudpebble, you can click ‘Build log’ to see precisely what caused the problem. What do you see there?

  20. Tyler said:

    Hey. I am just wondering if it is possible to edit a current watch face with this program to make some tweaks that I like? I like one particular watch face very much but I want to exchange the day for the date. How is it possible to ope up a .pbw file and edit it?

    • bonsitm said:

      Hi Tyler, it is not possible to edit a compiled pbw file, but you can find the source code on GitHub and made the modifications to recompile yourself.

  21. Why am I getting the time displayed as 01:23 instead of 1:23? Tried switching between 12/24 hour modes, but it seams to always display the first zero…

    • bonsitm said:

      Hi Robert. This may be to do with changes to localization that have been done since this post was made. If you manually manipulate the string, you can remove the zero if you prefer.

  22. John said:

    Err, is there something broken with the build on the cloudpebble?

    This code failed to compile:


    int main(void){


    Build number 1465394

    build output:

    Setting top to : /tmp/tmpPllVhi
    Setting out to : /tmp/tmpPllVhi/build
    Checking for program gcc,cc : arm-none-eabi-gcc
    Checking for program ar : arm-none-eabi-ar
    Found Pebble SDK in : /app/sdk2/Pebble
    ‘configure’ finished successfully (0.050s)
    Waf: Entering directory `/tmp/tmpPllVhi/build’
    [ 1/13] appinfo.json -> build/
    [ 4/13] app_resources.pbpack.manifest: ../../app/sdk2/Pebble/tools/ -> build/app_resources.pbpack.manifest
    [ 4/13] app_resources.pbpack.table: ../../app/sdk2/Pebble/tools/ -> build/app_resources.pbpack.table
    [ 4/13] ../../app/sdk2/Pebble/tools/ -> build/
    [ 6/13] ../../app/sdk2/Pebble/tools/ build/ -> build/src/
    [ 6/13] app_resources.pbpack: build/app_resources.pbpack.manifest build/app_resources.pbpack.table build/ -> build/app_resources.pbpack
    [ 7/13] c: src/main.c -> build/src/main.c.7.o
    [ 8/13] c: build/ -> build/
    [ 9/13] cprogram: build/src/main.c.7.o build/ -> build/pebble-app.elf
    [10/13] report-memory-usage: build/pebble-app.elf
    [11/13] pebble-app.raw.bin: build/pebble-app.elf -> build/pebble-app.raw.bin
    app memory usage:
    Total footprint in RAM: 138 bytes / ~24kb
    Free RAM available (heap): 24438 bytes

    [12/13] inject-metadata: build/pebble-app.raw.bin build/pebble-app.elf build/ -> build/pebble-app.bin
    Waf: Leaving directory `/tmp/tmpPllVhi/build’
    Build failed
    Traceback (most recent call last):
    File “/app/sdk2/Pebble/.waf-1.7.11-04343383b3a9511074383c51ae32d355/waflib/”, line 123, in process
    File “/app/sdk2/Pebble/.waf-1.7.11-04343383b3a9511074383c51ae32d355/waflib/”, line 47, in run
    return m1(self)
    File “/app/sdk2/Pebble/.waf-1.7.11-04343383b3a9511074383c51ae32d355/waflib/extras/”, line 132, in inject_data_rule
    inject_metadata.inject_metadata(tgt_path,elf_path,res_path,timestamp,allow_js=has_jsapp,has_worker=(worker_elf_file is not None))
    File “/app/sdk2/Pebble/.waf-1.7.11-04343383b3a9511074383c51ae32d355/waflib/extras/”, line 125, in inject_metadata
    File “/app/sdk2/Pebble/.waf-1.7.11-04343383b3a9511074383c51ae32d355/waflib/extras/”, line 62, in get_symbol_addr
    raise Exception(“Could not locate symbol in binary! Failed to inject app metadata”%(symbol))
    Exception: Could not locate symbol in binary! Failed to inject app metadata

    [ERROR ] A compilation error occurred

    • bonsitm said:

      John, you must at least call app_event_loop(). You may also need to push a Window or the app will immediately exit.

  23. John said:

    Ah, thx. It builds.

Leave a Reply

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

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

Google photo

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

Twitter picture

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

Facebook photo

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

Connecting to %s

%d bloggers like this: