2.0 PebbleKit Migration Guide

This guide provides you with a summary and a detailed list of the changes, updates and new APIs available in PebbleKit. To migrate your code successfully from Pebble SDK 1.x to Pebble SDK 2.x, you should read this guide.

In addition to updated and new Pebble APIs, you’ll find updated developer tools, beginning with a simplified build system that makes it easier to create, build, deploy, and test Pebble apps.

Applications written for Pebble SDK 1.x will not work when Pebble 2.0 is released. It is extremely important that you upgrade your apps, so that when Pebble 2.0 is released to the general public, your users can continue to enjoy your watchfaces and watchapps.

Upgrading to Pebble SDK 2.0

You can install Pebble SDK 2 in parallel with an older version of Pebble SDK. Both are compatible. Please follow the instructions in the Getting Started Guide.

These are the essential steps to perform the upgrade:

  • You’ll need to upgrade Pebble SDK on your computer, the firmware on your Pebble, and the Pebble mobile application on your phone.
  • You need to upgrade the arm-cs-tools. The version shipped with Pebble SDK 2 contains several important improvements that help reduce the size of the binaries generated and improve the performance of your app.
  • You need to upgrade the python dependencies (pip install --user -r ~/pebble-dev/PebbleSDK-2.0.2/requirements.txt).

Discovering the new Pebble tools

One of the new features introduced in Pebble SDK 2.0 is the pebble command line tool. This tool is used to create new apps, build and install those apps on your Pebble.

The tool was designed to simplify and optimize the build process for your Pebble watchface and watchapps. Give it a try right now:

$ pebble new-project helloworld
$ cd helloworld
$ ls
appinfo.json      resources    src          wscript

Notice that the new SDK does not require symlinks as the earlier SDK did. There is also a new appinfo.json file, described in greater detail later in this guide. The file provides you with a more readable format and includes all the metadata about your app.

$ pebble build
...

Memory usage:
=============
Total app footprint in RAM:        801 bytes / ~24kb
Free RAM available (heap):       23775 bytes

[12/13] inject-metadata: build/pebble-app.raw.bin build/app_resources.pbpack.data -> build/pebble-app.bin
[13/13] helloworld.pbw: build/pebble-app.bin build/app_resources.pbpack -> build/helloworld.pbw

...

'build' finished successfully (0.562s)

You don’t need to call the waf tool to configure and then build the project anymore (pebble still uses waf, however). The new SDK also gives you some interesting information on how much memory your app will use and how much memory will be left for you in RAM.

$  pebble install --phone 10.0.64.113 --logs
[INFO    ] Installation successful
[INFO    ] Enabling application logging...
[INFO    ] Displaying logs ... Ctrl-C to interrupt.
[INFO    ] D helloworld.c:58 Done initializing, pushed window: 0x2001a524

Installing an app with pebble is extremely simple. It uses your phone and the official Pebble application as a gateway. You do need to configure your phone first, however. For more information on working with this tool, refer to Configuring Pebble Tool).

You don’t need to run a local HTTP server or connect with Bluetooth like you did with SDK 1.x. You will also get logs sent directly to the console, which will make development a lot easier!

Upgrading a 1.x app to 2.0

Pebble 2.0 is a major release with many changes visible to users and developers and some major changes in the system that are not visible at first sight but will have a strong impact on your apps.

Here are the biggest changes in Pebble SDK 2.0 that will impact you when migrating your app. The changes are discussed in more detail below:

  • Every app now requires a appinfo.json, which includes your app name, UUID, resources and a few other new configuration parameters. For more information, refer to Anatomy of a Pebble app in the Pebble Developer Guide.
  • Your app entry point is called main() and not pbl_main().
  • Most of the system structures are not visible to apps anymore, and instead of allocating the memory yourself, you ask the system to allocate the memory and return a pointer to the structure.

    This means that you’ll have to change most of your system calls and significantly rework your app. This change was required to allow us to update the structs in the future (for example, to add new fields in them) without forcing you to recompile your app code.

  • Pebble has redesigned many APIs to follow standard C best practices and futureproof the SDK.

Application metadata

To upgrade your app for Pebble SDK 2.0, you should first run the pebble convert-project command in your existing 1.x project. This will automatically try to generate the appinfo.json file based on your existing source code and resource file. It will not touch your C code.

Please review your appinfo.json file and make sure everything is OK. If it is, you can safely remove the UUID and the PBL_APP_INFO in your C file.

Refer to Anatomy of a Pebble app for more information on application metadata and the basic structure of an app in Pebble SDK 2.0.

Pebble Header files

In Pebble SDK 1.x, you would reference Pebble header files with three include statements:

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

In Pebble SDK 2.x, you can replace them by one statement:

#include <pebble.h>

Initializing your app

In Pebble SDK 1.x, your app was initialized in a pbl_main() function:

void pbl_main(void *params) {
  PebbleAppHandlers handlers = {
    .init_handler = &handle_init
  };
  app_event_loop(params, &handlers);
}

In Pebble SDK 2.0:

  • pbl_main is replaced by main.
  • The PebbleAppHandlers structure no longer exists. You call your init and destroy handlers directly from the main() function.
int main(void) {
  handle_init();
  app_event_loop();
  handle_deinit();
}

There were other fields in the PebbleAppHandlers:

Opaque structures and Dynamic Memory allocation

In Pebble SDK 2, system structures are opaque and your app can’t directly allocate memory for them. Instead, you use system functions that allocate memory and initialize the structure at the same time.

Allocating dynamic memory: A simple example

In Pebble SDK 1.x, you would allocate memory for system structures inside your app with static global variables. For example, it was very common to write:

Window my_window;
TextLayer text_layer;

void handle_init(AppContextRef ctx) {
  window_init(&my_window, "My App");
  text_layer_init(&text_layer, GRect(0, 0, 144, 20));
}

In Pebble SDK 2, you can’t allocate memory statically in your program because the compiler doesn’t know at compile time how big the system structures are (here, in the above code snippet Window and TextLayer). Instead, you use pointers and ask the system to allocate the memory for you.

This simple example becomes:

Window *my_window;
TextLayer *text_layer;

void handle_init(void) {
  my_window = window_create();

  text_layer = text_layer_create(GRect(0, 0, 144, 20));
}

Instead of using _init functions and passing them a pointer to the structure, in SDK 2.0 you call functions that end in _create, and these functions will allocate memory and return to your app a pointer to a structure that is initialized.

Because the memory is dynamically allocated, it is extremely important that you release that memory when you are finished using the structure. This can be done with the _destroy functions. For our example, we could write:

void handle_deinit(void) {
  text_layer_destroy(text_layer);
  window_destroy(my_window);
}

Dynamic memory: General rules in Pebble SDK 2.0

  • Replace all statically allocated system structures by a pointer to the structure.
  • Replace functions that ended in _init by their equivalent that end in _create.
  • Keep pointers to the structures that you have initialized. Call the _destroy functions to release the memory.

AppMessage changes

For more information, please review AppMessage Reference Documentation and Integrating Pebble apps with Phone apps in the Pebble Developer Guide.

For working examples using AppMessage and AppSync in SDK 2.0, refer to:

  • PebbleSDK-2.0.2/PebbleSDK-2.x/Examples/pebblekit-js/quotes: Demonstrates how to use PebbleKit JS to fetch quote price from the web. It uses AppMessage on the C side.
  • PebbleSDK-2.0.2/PebbleSDK-2.x/Examples/pebblekit-js/weather: A PebbleKit JS version of the traditional weather-demo example. It uses AppSync on the C side.

Dealing with Tick events

Callbacks for tick events can’t be defined through PebbleAppHandlers anymore. Instead, use the Tick Event service with: tick_timer_service_subscribe(TimeUnits tick_units, TickHandler h).

For more information, refer to: Working with Event Services in the Pebble Developer Guide.

Timer changes

app_timer_send_event() is replaced by app_timer_register().

For more information, refer to Timer reference documentation.

WallTime API changes

  • PblTm has been removed and replaced by the libc standard struct. Use struct tm from #include <time.h>.

  • tm string_format_time() function is replaced by strftime().

  • get_time() is replaced by localtime(time(NULL)). This lets you convert a timestamp into a struct.

  • Pebble OS does not, as yet, support timezones. However, Pebble SDK 2 introduces gmtime() and localtime() functions to prepare for timezone support.

Click handler changes

In SDK 1.x, you would set up click handlers manually by modifying an array of config structures to contain the desired configuration. In SDK 2.x, how click handlers are registered and used has changed.

The following functions for subscribing to events have been added in SDK 2.x:

void window_set_click_context(ButtonId button_id, void *context);
void window_single_click_subscribe(ButtonId button_id, ClickHandler handler);
void window_single_repeating_click_subscribe(ButtonId button_id, uint16_t repeat_interval_ms, ClickHandler handler);
void window_multi_click_subscribe(ButtonId button_id, uint8_t min_clicks, uint8_t max_clicks, uint16_t timeout, bool last_click_only, ClickHandler handler);
void window_multi_click_subscribe(ButtonId button_id, uint8_t min_clicks, uint8_t max_clicks, uint16_t timeout, bool last_click_only, ClickHandler handler);
void window_long_click_subscribe(ButtonId button_id, uint16_t delay_ms, ClickHandler down_handler, ClickHandler up_handler);
void window_raw_click_subscribe(ButtonId button_id, ClickHandler down_handler, ClickHandler up_handler, void *context);

For more information, refer to Window User Interface.

For example, in SDK 1.x you would do this:

void click_config_provider(ClickConfig **config, void *context) {
    config[BUTTON_ID_UP]->click.handler = up_click_handler;
    config[BUTTON_ID_UP]->context = context;
    config[BUTTON_ID_UP]->click.repeat_interval_ms = 100;

    config[BUTTON_ID_SELECT]->click.handler = select_click_handler;

    config[BUTTON_ID_DOWN]->multi_click.handler = down_click_handler;
    config[BUTTON_ID_DOWN]->multi_click.min = 2;
    config[BUTTON_ID_DOWN]->multi_click.max = 10;
    config[BUTTON_ID_DOWN]->multi_click.timeout = 0; /* default timeout */
    config[BUTTON_ID_DOWN]->multi_click.last_click_only = true;

    config[BUTTON_ID_SELECT]->long_click.delay_ms = 1000;
    config[BUTTON_ID_SELECT]->long_click.handler = select_long_click_handler;
}

In SDK 2.x, you would use the following calls instead:

void click_config_provider(void *context) {
    window_set_click_context(BUTTON_ID_UP, context);
    window_single_repeating_click_subscribe(BUTTON_ID_UP, 100, up_click_handler);

    window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler);

    window_multi_click_subscribe(BUTTON_ID_DOWN, 2, 10, 0, true, down_click_handler);

    window_long_click_subscribe(BUTTON_ID_SELECT, 1000, select_long_click_handler, NULL /* No handler on button release */);
}

Notice that the signature of click_config_provider() has also changed. These window_*_click_subscribe() functions must be called from within the click_config_provider() function. If they are not, your app code will fail.

Other changes

  • graphics_text_draw() has been renamed to graphics_draw_text(), matching the rest of Pebble’s graphics_draw_ functions. There are no changes with the usage of the function.

Quick reference for the upgrader

Table 1. API changes from SDK 1.x to 2.x

API Call in SDK 1.x API Call in SDK 2.x
#define APP_TIMER_INVALID_HANDLE ((AppTimerHandle)0) Changed. No longer needed; app_timer_register always succeeds. See Time.
#define INT_MAX 32767 Changed. See #include <limits.h>
AppTimerHandle app_timer_send_event(AppContextRef app_ctx, uint32_t timeout_ms, uint32_t cookie); See app_timer_register for more information at Time.
ARRAY_MAX Removed from Pebble headers. Now use limits.h
bool app_timer_cancel_event(AppContextRef app_ctx_ref, AppTimerHandle handle); Changed. See app_timer_cancel for more information at Time.
GContext *app_get_current_graphics_context(void); Removed. Use the context supplied to you in the drawing callbacks.
GSize text_layer_get_max_used_size(GContext *ctx, TextLayer *text_layer); Use text_layer_get_content_size. See TextLayer.
INT_MAX Removed from Pebble headers. Now use limits.h
void get_time(PblTm *time); Use localtime(time(NULL)) from #include <time.h>.
void resource_init_current_app(ResVersionHandle version); No longer needed.
void string_format_time(char *ptr, size_t maxsize, const char *format, const PblTm *timeptr); Use strftime from #include <time.h>.
void window_render(Window *window, GContext *ctx); No longer available.

Using _create()/_destroy() instead of _init()/_deinit() functions

If you were using the following _init()/_deinit() functions, you should now use _create()/_destroy() instead when making these calls:

  • bool rotbmp_init_container(int resource_id, RotBmpContainer *c); See BitmapLayer.
  • bool rotbmp_pair_init_container(int white_resource_id, int black_resource_id, RotBmpPairContainer *c); BitmapLayer.
  • void action_bar_layer_init(ActionBarLayer *action_bar); See ActionBarLayer.
  • void animation_init(struct Animation *animation); See Animation.
  • void bitmap_layer_init(BitmapLayer *bitmap_layer, GRect frame); BitmapLayer.
  • void gbitmap_init_as_sub_bitmap(GBitmap *sub_bitmap, const GBitmap *base_bitmap, GRect sub_rect); See Graphics Types.
  • void gbitmap_init_with_data(GBitmap *bitmap, const uint8_t *data); See Graphics Types.
  • void inverter_layer_init(InverterLayer *inverter, GRect frame); See InverterLayer.
  • void layer_init(Layer *layer, GRect frame); See Layers.
  • void menu_layer_init(MenuLayer *menu_layer, GRect frame); See MenuLayer.
  • void number_window_init(NumberWindow *numberwindow, const char *label, NumberWindowCallbacks callbacks, void *callback_context);
  • void property_animation_init_layer_frame(struct PropertyAnimation *property_animation, struct Layer *layer, GRect *from_frame, GRect *to_frame); See Animation.
  • void property_animation_init(struct PropertyAnimation *property_animation, const struct PropertyAnimationImplementation *implementation, void *subject, void *from_value, void *to_value); See Animation.
  • void rotbmp_deinit_container(RotBmpContainer *c); BitmapLayer.
  • void rotbmp_pair_deinit_container(RotBmpPairContainer *c); BitmapLayer.
  • void scroll_layer_init(ScrollLayer *scroll_layer, GRect frame); See ScrollLayer.
  • void simple_menu_layer_init(SimpleMenuLayer *simple_menu, GRect frame, Window *window, const SimpleMenuSection *sections, int num_sections, void *callback_context); See SimpleMenuLayer.
  • void text_layer_deinit(TextLayer *text_layer); See TextLayer.
  • void text_layer_init(TextLayer *text_layer, GRect frame); See TextLayer.
  • void window_deinit(Window *window); See Window User Interface.
  • void window_init(Window *window); See Window User Interface.